Java框架之SpringSecurity 08-权限系统


SpringSecurity

  SpringSecurity融合Spring技术栈,提供JavaEE应用的整体安全解决方案;提供全面的安全服务。Spring Security支持广泛的认证模型

 模块划分

Core - spring-security-core.jar

核心模块:核心认证、授权功能、支持jdbc-user功能、支持独立的Spring应用

Remoting - spring-security-remoting.jar

远程交互模块:一般不需要,可以使用Spring Remoting功能简化远程客户端交互

Web - spring-security-web.jar

web安全模块:web项目使用,基于URL的访问控制(access-control)

Config - spring-security-config.jar

java配置模块:必须依赖包,包含解析xml方式和java 注解方式来使用SpringSecurity功能

LDAP - spring-security-ldap.jar

ldap(轻量目录访问协议)支持模块:可选依赖包,LDAP功能支持

ACL - spring-security-acl.jar

ACL支持:ACL(Access-Control-List)访问控制列表。细粒度的资源访问控制(RBAC+ACL)

CAS - spring-security-cas.jar

CAS整合支持:CAS(Central Authentication Service)中央认证服务。开源ApereoCAS整合

OpenID - spring-security-openid.jar

OpenID 认证方式: 用于针对外部服务器对用户进行身份验证(微信,新浪微博第三方登录)

Test - spring-security-test.jar

测试模块:快速的测试SpringSecurity应用

 基于Maven Web工程实例

   添加 security-pom 依赖

    
    <dependency>
      <groupId>org.springframework.securitygroupId>
      <artifactId>spring-security-webartifactId>
      <version>4.2.10.RELEASEversion>
    dependency>
    <dependency>
      <groupId>org.springframework.securitygroupId>
      <artifactId>spring-security-configartifactId>
      <version>4.2.10.RELEASEversion>
    dependency>
    
    <dependency>
      <groupId>org.springframework.securitygroupId>
      <artifactId>spring-security-taglibsartifactId>
      <version>4.2.10.RELEASEversion>
    dependency>

   web.xml 中添加 SpringSecurity的 Filter 进行安全控制

  
  <servlet>
    <servlet-name>springDispatcherServletservlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
    <init-param>
      <param-name>contextConfigLocationparam-name>
      <param-value>
        classpath*:/spring/springmvc.xml
        classpath*:/spring/spring-*.xml
      param-value>
    init-param>
    <load-on-startup>1load-on-startup>
  servlet>

  <filter>
    <filter-name>springSecurityFilterChainfilter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
  filter>
  <filter-mapping>
    <filter-name>springSecurityFilterChainfilter-name>
    <url-pattern>/*url-pattern>
  filter-mapping>

   SpringSecurity 配置类

/**
 * @Configuration   管理程序中的组件(扫描)
 * @EnableWebSecurity   安全框架支持注解的形式             基础注解
* @EnableGlobalMethodSecurity 开启使用表达式方法验证安全性
*/ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class AppWebSecurityConfig extends WebSecurityConfigurerAdapter { @Override //认证 protected void configure(AuthenticationManagerBuilder auth) throws Exception { } @Override //授权 protected void configure(HttpSecurity http) throws Exception { } }

  查看登录页面的源码,有个标签<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> 这是SpringSecurity 帮我们防止“跨站请求伪造”攻击;还可以防止表单重复提交。此标签 value 值会动态生成一个令牌值当用户请求登录时会验证此令牌值的正确性。如果想禁用此功能可在配置类中设置 http.csrf().disable();

 l  令牌值变化:

n  如果登录成功(用户名,密码正确),令牌会被删除,

n  重新回到登录页后退网页,令牌会重新生成;

n  如果登录失败(用户名,密码错误),令牌不变。

n  刷新登录页,令牌值也不变

认证

    @Autowired
    private UserDetailsService  userDetailsService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //从数据库中查询数据        
      auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }

 UserDetailsService

  此接口由 Security 来调用基于 AOP 模式进行权限检查,返回一个 UserDetails 接口类型其存放着该用户从数据库查询出来的所有权限信息

步骤:
  1 在业务层实现 UserDetailsService 接口通过用户名从 Dao 层查询出该用户对象
  2 创建一个 HashSet 接口类型的集合,该 GrantedAuthority 类型用来存放角色和权限信息,它的实现类 SimpleGrantedAuthority 需要传入字符串类型角色名和权限名
  3 通过该用户 id 查询出该用户所拥有的角色集合
  4 通过该用户 id 查询出该用户所拥有的权限集合
  5 通过所有角色名和所有权限名 创建 SimpleGrantedAuthority 对象并添加到 HashSet 集合中
  6 创建 User 类对象,该对象实现了 UserDetails 接口,为此 user 对象传入该用户的用户名和密码加上权限集合 Set 并返回该对象即可
    @Autowired //用户
    private TAdminMapper adminMapper;
    @Autowired //角色
    private TRoleMapper roleMapper;
    @Autowired //权限
    private TPermissionMapper permissionMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        TAdminExample example = new TAdminExample();
        TAdminExample.Criteria criteria = example.createCriteria();
        criteria.andLoginacctEqualTo(username);
        List admins = adminMapper.selectByExample(example);
        //从数据库中查出该用户
        TAdmin admin = admins.get(0);
        //该集合用来存放角色和权限
        HashSet authorities = new HashSet<>();
        //从数据库中查出该用户所对应的角色
        List roles = roleMapper.listRole(admin.getId());
        //从数据库中查出该用户所对应的权限
        List permissions = permissionMapper.listPermission(admin.getId());
        //分别将角色和权限添加到 authorities 集合中
        for (TRole role : roles) {
            String name = role.getName();
            authorities.add(new SimpleGrantedAuthority("ROLE_" + name));//注意角色需加上 "ROLE_"
        }
        permissions.forEach((p) -> {
            String name = p.getName();
            authorities.add(new SimpleGrantedAuthority(name));
        });
        //通过该用户名和密码以及权限集合创建User对象并返回
        User user = new User(admin.getLoginacct().toString(), admin.getUserpswd().toString(), authorities);
        return user;
    }

授权

 HttpSecurity 该类允许对特定的http请求基于安全考虑进行配置。默认情况下,适用于所有的请求.亦通过该对象 http 方法为用户配置精细化权限访问控制

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //基于httpRequest对指定antMatchers资源permitAll放行,对于其他请求anyRequest必须通过认证authenticated
        http.authorizeRequests().antMatchers("/welcome.jsp","/static/**")
                .permitAll().anyRequest().authenticated();
        //跳转到默认登录界面
        http.formLogin().loginPage("/welcome.jsp");
        //登录时指定的控制器/login,并验证用户名和密码,成功验证后跳转到控制器/main
        http.formLogin().loginProcessingUrl("/login")
                .usernameParameter("loginacct")
                .passwordParameter("userpswd")
                .defaultSuccessUrl("/main");
        //取消csrf令牌值验证
        http.csrf().disable();
        //退出时指定的控制器,并指定成功退出后登录界面
        http.logout().logoutUrl("/exit").logoutSuccessUrl("/welcome.jsp");
        //记住我功能.需在前端复选框中指定 value 值为 remember-me
        http.rememberMe();
        //自定义的异常处理器
        http.exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {
            @Override
            public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
                //判断是否为异步请求
                if("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))){
                    response.getWriter().write("403");
                }else {
                    request.setAttribute("msg",accessDeniedException.getMessage());
                    request.getRequestDispatcher("/WEB-INF/views/unauth.jsp")
                            .forward(request,response);
                }
            }
        });
    }

  通过方法调用可以更精细化控制访问权限   

    authorizeRequests():返回一个配置对象用于配置请求的访问限制
    formLogin():返回表单配置对象,当什么都不指定时会提供一个默认的,如配置登录请求,还有登录成功页面
    logout():返回登出配置对象,可通过logoutUrl设置退出url
    antMatchers:匹配请求路径或请求动作类型,如:.antMatchers("/admin/**")
    addFilterBefore: 在某过滤器之前添加 filter
    addFilterAfter:在某过滤器之后添加 filter
    addFilterAt:在某过滤器相同位置添加 filter,不会覆盖相同位置的 filter
    hasRole:结合 antMatchers 一起使用,设置请求允许访问的角色权限或IP

方法名        

用途

access(String)        

SpringEL表达式结果为true时可访问

anonymous()        

匿名可访问

denyAll()        

用户不可以访问

fullyAuthenticated()        

用户完全认证访问(非remember me下自动登录)

hasAnyAuthority(String…)        

参数中任意权限可访问

hasAnyRole(String…)        

参数中任意角色可访问

hasAuthority(String)        

某一权限的用户可访问

hasRole(String)        

某一角色的用户可访问

permitAll()        

所有用户可访问

rememberMe()        

允许通过remember me登录的用户访问

authenticated()        

用户登录后可访问

hasIpAddress(String)        

用户来自参数中的IP可访问

SpEL 表达式方法级别的安全性         4个注解可用

@PreAuthorize 在方法执行之前检查,基于表达式的计算结果来限制对方法的访问   //@PreAuthorize("hasRole('软件工程师')")

@PostAuthorize 在方法执行后检查,但是如果表达式计算结果为false,将抛出一个安全性异常

@PostFilter 允许方法调用,但必须按照表达式来过滤方法的结果

@PreFilter 允许方法调用,但必须在进入方法之前过滤输入值

Security 标签

  在 jsp 页面还可通过标签进一步控制 html 标签的访问权限或获取该用户信息 : 引入标签库

<%@taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
  <sec:authentication property="name"/> //在需要位置显示用户登录名, 属性 property 必须是 name
   <sec:authorize access="hasRole('PM - 项目经理')">//非此角色用户隐藏下面的标签
       <button type="button" id="deleteBath" class="btn btn-danger" style="float:right;margin-left:10px;">删除button>
   sec:authorize>