SpringBoot集成Security框架 2022.3.24

SpringBoot集成Security框架

1、核心配置详解

1)SecurityConfig(security配置):主要用来对Security整体进行配置,要继承WebSecurityConfigurerAdapter类,在configure方法中可以对跨域,接口请求资源放行,异常处理,过滤器进行配置。也可以在类中通过 @Bean注解注入所要使用的Bean到容器中进行使用,比如AuthenticationManager(用来对用户的认证)和PasswordEncoder(对密码进行加密)类。
2)JwtFilter(jwt过滤器):在用户名和密码过滤器(UsernamePasswordAuthenticationFilter(security自带的))之前对请求进行拦截,对请求携带的token进行拦截判断。JwtFilter类继承OncePerRequestFilter类并实现doFilterInternal方法,对请求进行过滤。
3)UserDetailsServiceImpl:需要实现UserDetailsService类,目的是用来完成用户在登录对数据库用户的查询,在登录方法中AuthenticationManager(认证管理器)会自动调用这个实现类进行查询。UserDetailsServiceImpl的返回结果应该是UserDetails的实现类,因此需要单独写一个类UserDetailImpl来实现UserDetails(用来封装用户信息)。
4)UserDetailImpl:用来对用户认证身份进行封装,包括权限、用户名,密码、账号过期锁定等等。用来把autenticate.getPrincipal()对象转换成UserDetailImpl的用户对象,这里之所以这样,是因为security内部是通过UserDetails实现的,我们实现了UserDetails这个类,所以可以用UserDetailImpl进行封装。
5)WebUtils(封装了统一的返回方法):用来对security发生异常时,对数据的返回
6)AccessDeniedHandlerImpl:访问拒绝的实现类,用来对访问拒绝的异常进行返回(主要是权限异常)。需要实现AccessDeniedHandler,并且实现其中的handle方法。这个方法中会调用WebUtils中的方法。
6)AuthenticationEntryPointImpl:处理认证异常的实现类,需要实现AuthenticationEntryPoint类,用来对认证过程中的异常信息进行处理并返回,需要实现commence方法,方法中会调用WebUtils中方法。

2、核心配置代码

1)SecurityConfig
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Resource
    private JwtFilter jwtFilter;

    @Resource
    AuthenticationEntryPoint authenticationEntryPoint;
    @Resource
    AccessDeniedHandler accessDeniedHandler;

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf().disable()
                //不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 对于登录接口 允许匿名访问
                //.antMatchers("/login").anonymous()

        /**
         * 放行swagger资源和登录资源
         */
                //.antMatchers("/login","/doc.html","/webjars/**","/img.icons/**","/swagger-resources/**","/**","/v2/api-docs").permitAll()
                //.anyRequest().authenticated();
//                .antMatchers("/user/*").authenticated()
                //jwt过滤器测试用,如果测试没有问题吧这里删除了
//                .antMatchers("/link/getAllLink").authenticated()
//                .antMatchers("/user/*").authenticated()
                // 除上面外的所有请求全部不需要认证即可访问
                .antMatchers("/api/user/login","/api/user/regist").permitAll()
                .antMatchers("/api/user/**").authenticated()
                .anyRequest().permitAll();
        //配置异常处理器
        http.exceptionHandling()
            //认证异常
                .authenticationEntryPoint(authenticationEntryPoint)
            //权限异常
                .accessDeniedHandler(accessDeniedHandler);
        //关闭默认的退出登录功能
        http.logout().disable();
        //把jwtAuthenticationTokenFilter添加到SpringSecurity的过滤器链中
        http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
        //允许跨域
        http.cors();
    }
}
2)JwtFilter
@Slf4j
@Component
public class JwtFilter extends OncePerRequestFilter {
    @Autowired
    IUserService userService;
    @Autowired
    private JwtUtil jwtUtil;
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {

//        String requestURI = httpServletRequest.getRequestURI();
//        log.warn(requestURI);
        String token = httpServletRequest.getHeader("token");
        if(StrUtil.hasEmpty(token)){
            //如果没有携带token,那么直接放行,这里需要对没有携带token的请求都放行,需要认证的请求可以在securityConfig中进行配置认证
            filterChain.doFilter(httpServletRequest,httpServletResponse);
            return;

        }
        Claims claims = null;

        try {
            //解析token
            claims = jwtUtil.parseJWT(token);
        }catch (Exception e){
            //token异常
            e.printStackTrace();
            ResponseResult result = ResponseResult.fail(ResponseCode.TOKEN_ERROR.code,ResponseCode.TOKEN_ERROR.msg);
            WebUtils.renderString(httpServletResponse, StrUtil.toString(result));
            return;
        }
        //获取token中存的关于用户的信息
        String userId = claims.getSubject();
        User user = userService.getById(Long.valueOf(userId));
        if(user==null){
            ResponseResult result = ResponseResult.fail(ResponseCode.FAIL.code,"用户不存在");
            WebUtils.renderString(httpServletResponse, JSONUtil.toJsonStr(result));
            return;
        }
        //交给security进行管理
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user,token,null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        //放行过滤器
        filterChain.doFilter(httpServletRequest,httpServletResponse);

    }
}
3)UserDetailsServiceImpl
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Resource
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        //通过用户名查找用户
        User user = userMapper.selectOne(queryWrapper.eq("user_name", username));
        if(Objects.isNull(user)){
            throw new UsernameNotFoundException("用户不存在");
        }
        //返回封装认证用户类
        return new UserDetailImpl(user);
    }
}
4)UserDetailImpl
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserDetailImpl implements UserDetails {
    private User user;
    //权限
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return user.getUserPass();
    }

    @Override
    public String getUsername() {
        return user.getUserName();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}
5)WebUtils
public class WebUtils
{
    /**
     * 将字符串渲染到客户端
     *
     * @param response 渲染对象
     * @param string 待渲染的字符串
     * @return null
     */
    public static void renderString(HttpServletResponse response, String string) {
        try
        {
            response.setStatus(200);
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            response.getWriter().print(string);
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }


    public static void setDownLoadHeader(String filename, ServletContext context, HttpServletResponse response) throws UnsupportedEncodingException {
        String mimeType = context.getMimeType(filename);//获取文件的mime类型
        response.setHeader("content-type",mimeType);
        String fname= URLEncoder.encode(filename,"UTF-8");
        response.setHeader("Content-disposition","attachment; filename="+fname);

//        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
//        response.setCharacterEncoding("utf-8");
    }
}
6)AccessDeniedHandlerImpl
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        //打印下异常
        accessDeniedException.printStackTrace();
        ResponseResult result = ResponseResult.fail(ResponseCode.UNAUTHORI.code,ResponseCode.UNAUTHORI.msg);
        //以json的形式相应给前端
        WebUtils.renderString(response, JSONUtil.toJsonStr(result));
    }
}
7)AuthenticationEntryPointImpl
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        //打印下异常
        authException.printStackTrace();
        //登录认证是根据不同的情况,提示不同的错误
        ResponseResult result = null;
        //BadCredentialsException密码错误的异常,坏的凭据
        //InsufficientAuthenticationException,凭据不足
        if(authException instanceof BadCredentialsException){
            result = ResponseResult.fail(ResponseCode.FAIL.code,authException.getMessage());
        }else if(authException instanceof InsufficientAuthenticationException){
            result = ResponseResult.fail(ResponseCode.UNLOGIN.code,authException.getMessage());
        }else{
            result = ResponseResult.fail(ResponseCode.FAIL.code,"认证和授权出线问题");
        }
        //以json的形式相应给前端
        WebUtils.renderString(response, JSONUtil.toJsonStr(result));
    }
}

3、注意事项

在使用UsernamePasswordAuthenticationToken进行用户和密码认证时,需要调用AuthenticationManager认证管理器进行管理,在这个过程中一定不能忘记实现UserDetailsServiceImpl和UserDetailImpl类,不然会走默认的认证流程,这时候就查询不到用户数据,会报出Handler dispatch failed; nested exception is java.lang.StackOverflowError这个异常

4、自定义登录

@Service//这里是user的业务层,用的mybatisplus
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public ResponseResult login(UserDto userDto) {
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDto.getUserName(),userDto.getUserPass());
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        if(Objects.isNull(authenticate)){
//            throw new BadCredentialsException("用户名或密码错误");
            return ResponseResult.fail(ResponseCode.FAIL.code,"账号或密码错误");
        }
        UserDetailImpl userDetail = (UserDetailImpl)authenticate.getPrincipal();
        String userId = userDetail.getUser().getUserId().toString();
        String jwt = JwtUtil.createJWT(userId);
        return ResponseResult.success(MapUtil.builder().put("token",jwt).build());
    }
}

版权声明:本文为weixin_44357065原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。