Spring Security

  • 认证和授权

    用户登录要进行认证。

    用户进行增删改查具体的操作,要进行授权。

  • 实现功能

    1. 用户认证授权信息【通过实现UserDetailsService接口】返回一个User对象(封装了authorities[权限])。

       /*
          在login时Spring Security会调用该方法,并将用户名自动传入到方法中。
           */
          @Override
          public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
              //根据用户名查询Admin对象。
              Admin admin = adminService.getByUsername(username);
              if(null == admin) {
                  throw new UsernameNotFoundException("用户名不存在!");
              }
              //获取权限的编码,给用户授权。
              //获取用户应该有的权限
              List<String> codeList = permissionService.findCodeListByAdminId(admin.getId());
              //获取权限的集合。
              Collection<GrantedAuthority> authorities = new ArrayList<>();
              for(String code : codeList) {
                  if(StringUtils.isEmpty(code)) continue;
                  SimpleGrantedAuthority authority = new SimpleGrantedAuthority(code);
                  authorities.add(authority);
              }
              //
              return new User(username,admin.getPassword(),authorities);
          }
      

      PermissionService:获取权限列表

      @Override
          public List<String> findCodeListByAdminId(Long adminId) {
              //超级管理员admin账号id为:1
              if(adminId.longValue() == 1) {
                  return permissionDao.findAllCodeList();
              }
              return permissionDao.findCodeListByAdminId(adminId);
          }
      
    2. 实现用户授权,也就是只有授权后才可以使用该功能。

      • 开启Controller方法权限控制

        WebSecurityConfig的类上加@EnableGlobalMethodSecurity注解, 来判断用户对某个控制层的方法是否具有访问权限

        @EnableGlobalMethodSecurity(prePostEnabled = true)
        

        之后在Controller方法上添加注解

        //只有当有这个权限码时,才可以使用这个方法。
        @PreAuthorize("hasAuthority('role.show')")
           @RequestMapping
           public String index(ModelMap model, HttpServletRequest request) {
               Map<String,Object> filters = getFilters(request);
               PageInfo<Role> page = roleService.findPage(filters);
        
               model.addAttribute("page", page);
               model.addAttribute("filters", filters);
               return PAGE_INDEX;
           }
        
      • 开启按钮权限控制

        直接调用Spring Security的标签库即可。需要开启标签支持以及Jar包导入

        <!-- 添加spring security 标签支持:sec -->
                <property name="additionalDialects">
                    <set>
                        <bean class="org.thymeleaf.extras.springsecurity5.dialect.SpringSecurityDialect" />
                    </set>
                </property>
        

        只有在有权限时,按钮才可用。

                                            <a class="edit" th:attr="data-id=${item.id}" sec:authorize="hasAuthority('role.edit')">修改</a>
                                            <a class="delete" th:attr="data-id=${item.id}" sec:authorize="hasAuthority('role.delete')">删除</a>
                                            <a class="assgin" th:attr="data-id=${item.id}" sec:authorize="hasAuthority('role.assgin')">分配权限</a>
        
    3. 使用指定的登录页面进行登录。【WebSecurity实现】

    4. 配置可以访问的静态资源。【WebSecurity实现】

    5. 对密码进行加密。【WebSecurity实现 + admin新增用户时实现】

    //WebSecurity中,我们制定了BCryptPasswordEncoder 密码加密器进行加密和校验,所以我们密码必须要在加密后进行存储。这样才能匹配成功。所以admin在实现新增用户时,我们要给admin密码进行加密。
    @Bean
        public PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
    
  • WebSecurity

    添加依赖,配置Filter,配置Spring Security三个步骤,就可以集成使用Spring Security。

    在使用Spring Security之前,需要配置,这里使用Java类配置的方法。

    @Configuration //声明当前类是一个配置类
    @EnableWebSecurity //@EnableWebSecurity是开启SpringSecurity的默认行为
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //允许iframe嵌套显示
            http.headers().frameOptions().disable();
            //登录设置
            http
                    .authorizeRequests()
                    .antMatchers("/static/**","/login").permitAll()  //允许匿名用户访问的路径
                    .anyRequest().authenticated()    // 其它页面全部需要验证
                    .and()
                    .formLogin()
                    .loginPage("/login")    //用户未登录时,访问任何需要权限的资源都转跳到该路径,即登录页面,此时登陆成功后会继续跳转到第一次访问的资源页面(相当于被过滤了一下)
                    .defaultSuccessUrl("/") //登录认证成功后默认转跳的路径,意思时admin登录后也跳转到/user
                    .and()
                    .logout()
                    .logoutUrl("/logout")   //退出登陆的路径,指定spring security拦截的注销url,退出功能是security提供的
                    .logoutSuccessUrl("/login")//用户退出后要被重定向的url
        		    .and()
                //因为Spring Security登录页面是有一个token字符串的,来标识一个网页,如果不关闭,就没有这个token(身份验证令牌)。就会在logout时报错。
                //
                    .csrf().disable();//关闭跨域请求伪造功能
    
                    //添加自定义异常入口
                    http.exceptionHandling().accessDeniedHandler(new CustomAccessDeineHandler());
    
        }
        /**
         * 必须指定加密方式,上下加密方式要一致
         * @return
         */
        @Bean
        public PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
    }
    
    • 友好提示

      用户没有权限直接返回404吗?不是,这里自定义异常处理,使用AccessDeniedHandler,返回一个错误页面,更友好。

      AccessDeniedHandler 该类用来统一处理 AccessDeniedException 异常(主要是在用户在访问受保护资源时被拒绝而抛出的异常)

      //添加自定义异常入口
                      http.exceptionHandling().accessDeniedHandler(new CustomAccessDeineHandler());
      
      /**
       * 未授权的统一处理方式
       *
       */
      public class CustomAccessDeineHandler implements AccessDeniedHandler {
      
          @Override
          public void handle(HttpServletRequest request, HttpServletResponse response,
                             AccessDeniedException accessDeniedException) throws IOException {
              response.sendRedirect("/auth");
          }
      
      }
      

04-06 23:48