集成spring security


一 引入依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>    


二 新建配置类


@Configuration
@EnableMethodSecurity
@EnableWebSecurity
public class SecurityCfg {

    @Value("${server.cors}")
    private Boolean isCors;
    @Autowired
    private JwtFilter jwtFilter;


    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {

        httpSecurity
                .csrf(httpSecurityCsrfConfigurer -> httpSecurityCsrfConfigurer.disable())
                .formLogin(httpSecurityFormLoginConfigurer -> httpSecurityFormLoginConfigurer.disable())
                .httpBasic(httpSecurityHttpBasicConfigurer -> httpSecurityHttpBasicConfigurer.disable())
                .logout(httpSecurityLogoutConfigurer -> httpSecurityLogoutConfigurer.disable());

        if (isCors!=null && isCors){
            httpSecurity.cors(configurer -> configurer.configurationSource(request -> {
                CorsConfiguration corsConfiguration = new CorsConfiguration();
                corsConfiguration.addAllowedOrigin("*");
                corsConfiguration.addAllowedMethod("*");
                corsConfiguration.addAllowedHeader("*");
                return corsConfiguration;
            }));
        }

        httpSecurity.addFilterAt(jwtFilter, UsernamePasswordAuthenticationFilter.class);
        httpSecurity.addFilterBefore(new ExceptionFilter(),JwtFilter.class);
        //httpSecurity.addFilterAt(anonymousFilter, AnonymousAuthenticationFilter.class);
        
        httpSecurity.sessionManagement(httpSecuritySessionManagementConfigurer -> 
                httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
                
        httpSecurity.authorizeHttpRequests(auth-> auth
                .requestMatchers("/swagger-ui/**").permitAll()
                .requestMatchers("/css/**","/js/**","/favicon.ico",
                        "/robots.txt","/manifest.json","/icon.png","/avatar.png","/sw.js"
                ).permitAll() //静态资源
                //访问都需要VIEW权限
                .anyRequest().hasAuthority("VIEW")
        );

        httpSecurity.exceptionHandling(configurer -> {
            configurer.authenticationEntryPoint((request, response, accessDeniedException) -> {
                throw new BadCredentialsException("未认证");
            });
            configurer.accessDeniedHandler((request, response, accessDeniedException) -> {
                throw new AuthenticationException("未授权");
            });
        });
        return httpSecurity.build();
    }

}


三 实现 UserDetailsService 接口

org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration

spring boot的 org.springframework.boot.autoconfigure.AutoConfiguration.imports 里引入该自动配置类.
当没有 ConditionalOnMissingBean 指定的这几个类时,会自动配置一个 InMemoryUserDetailsManager.所以只要实现一个 UserDetailsService 类就可以了.

@AutoConfiguration
@ConditionalOnClass(AuthenticationManager.class)
@Conditional(MissingAlternativeOrUserPropertiesConfigured.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean(value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class,
		AuthenticationManagerResolver.class }, type = "org.springframework.security.oauth2.jwt.JwtDecoder")
@ConditionalOnWebApplication(type = Type.SERVLET)
public class UserDetailsServiceAutoConfiguration {

	@Bean
	public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties,
			ObjectProvider<PasswordEncoder> passwordEncoder) {
		SecurityProperties.User user = properties.getUser();
		List<String> roles = user.getRoles();
		return new InMemoryUserDetailsManager(User.withUsername(user.getName())
			.password(getOrDeducePassword(user, passwordEncoder.getIfAvailable()))
			.roles(StringUtils.toStringArray(roles))
			.build());
	}
}
 
@Service
public class UserService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return null;
    }
}


四 使用jwt

1. 引入依赖

        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.19.2</version>
        </dependency>

2.


/**
 * 从请求头获取token并装载用户
 */
@Component
public class JwtFilter extends OncePerRequestFilter {
    @Autowired
    private UserService userService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        //获取访问ip
        String fuck = request.getHeader("X-Forwarded-For");
        System.out.println("请求ip:"+fuck);

        Enumeration<String> headers = request.getHeaderNames();
        if (headers!=null){
            String tmp = "";
            while (headers.hasMoreElements()){
                tmp = tmp+" "+headers.nextElement();
            }
            System.out.println("gan:"+tmp);
        }

        //从header获取token
        String token = request.getHeader("Authentication");

        //header中没有token,则从cookie获取token
        if (token==null || token.isEmpty()){
            Cookie[] cookies = request.getCookies();
            if (cookies==null || cookies.length==0){
                filterChain.doFilter(request,response);
                return;
            }
            for (Cookie cookie:cookies){
                if (cookie.getName().equals("token")){
                    token = cookie.getValue();
                    break;
                }
            }
            //cookie中也没有token,则交给下一个过滤器
            if (token==null || token.equals("")){
                filterChain.doFilter(request,response);
                return;
            }
        }
        DecodedJWT decodedJWT = null;
        try {
            decodedJWT = JWT.decode(token);
        }catch (Exception e){
            return;
        }
        List<String> audience = decodedJWT.getAudience();
        String username = audience.getFirst();

        //用户密码作为加密盐
        if (username == null){
            throw new BadCredentialsException("认证信息缺失");
        }

        UserInfo userInfo = userService.getUserByUsername(username);
        if (userInfo == null){
            throw new BadCredentialsException("用户不存在");
        }
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(userInfo.getPassword())).build();
        try {
            jwtVerifier.verify(token);
        }catch (SignatureVerificationException e){
            e.printStackTrace();
            response.sendError(401);
            return;
        }catch (TokenExpiredException e){
            response.setStatus(401);
            return;
        }

        Set<GrantedAuthority> authorities = new HashSet<>();
        for (UserRole role:userInfo.getRoles()){
            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(role.getRole());
            authorities.add(simpleGrantedAuthority);
        }
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                username,
                userInfo.getPassword(),
                authorities
        );
        authenticationToken.setDetails(userInfo);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        filterChain.doFilter(request,response);
    }
}



异常处理

spring boot @RestControllerAdvice 定义的全局异常捕获,只能捕获 servlet(Controller) 后的异常.

所以,在Controller上定义的hasAuthority,如果未登陆或者没有权限,可以交给全局异常处理程序处理.

Spring Security配置文件里,JwtFilter中的异常,最好直接抛出,新建一个ExceptionFilter来处理.


public class ExceptionFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            filterChain.doFilter(request,response);
        }catch (Exception e){
            if (e instanceof DisabledException){
                //已被封禁 请到 /block 查看封禁原因,如继续有疑问请联系客服.
            }
            else if (e instanceof BadCredentialsException){
                //未登录 需要登录后再继续
                response.setContentType("application/json;charset=UTF-8");
                response.setStatus(401);
                response.getWriter().write("");
            }
            else if (e instanceof AuthenticationException){
                //没有权限 没有权限进行该操作
                response.setContentType("application/json;charset=UTF-8");
                response.setStatus(403);
                PrintWriter printWriter = response.getWriter();
                printWriter.write("");
                printWriter.flush();
            }
            else {
                e.printStackTrace();
            }
        }
    }
}




@RestControllerAdvice
public class ExceptionHandle {


    //无权限
    @ExceptionHandler(AccessDeniedException.class)
    public void denied(HttpServletRequest request, HttpServletResponse response){
        ResMap<String> resMap = ResMap.fail("没有权限进行该操作");
        resMap.setCode(403);
        response.setStatus(403);
        response.setContentType("application/json;charset=UTF-8");
        try {
            String tem = new ObjectMapper().writeValueAsString(resMap);
            response.getWriter().write(tem);
        }catch (Exception ignore){}
    }


    //未登录
    @ExceptionHandler(AuthenticationException.class)
    public void authenticationException(HttpServletResponse response){
        //未登录
        ResMap<String> resMap = ResMap.fail("需要登录后再继续");
        resMap.setCode(401);
        response.setStatus(401);
        response.setContentType("application/json;charset=UTF-8");
        try {
            String tem = new ObjectMapper().writeValueAsString(resMap);
            response.getWriter().write(tem);
        }catch (Exception ignore){}
    }

    //无Square权限
    @ExceptionHandler(SquareDisableException.class)
    public void squareDisabled(Exception e,HttpServletResponse response){
        ResMap<String> resMap = ResMap.fail("square功能未开启");
        resMap.setCode(315);
        response.setStatus(315);
        response.setContentType("application/json;charset=UTF-8");
        try {
            String tem = new ObjectMapper().writeValueAsString(resMap);
            response.getWriter().write(tem);
        }catch (Exception ignore){}
    }

    @ExceptionHandler(Exception.class)
    public void fuck(Exception e,HttpServletResponse response){
        e.printStackTrace();
//        System.out.println("fuck2 ");
        response.setStatus(400);
    }
}