SpringBoot3配置SpringSecurity6

本文介绍了如何在SpringBoot项目中集成SpringSecurity,实现JWT登录验证,以及设置匿名访问和跨域资源共享(CORS)策略。详细解读了WebSecurityConfiguration中的配置和自定义异常处理类。
摘要由CSDN通过智能技术生成

访问1:localhost:8080/security,返回:需要先认证才能访问(说明没有权限)

访问2:localhost:8080/anonymous,返回:anonymous(说明正常访问)


 

相关文件如下:

pom.xml:

   <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>


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

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.18</version>
        </dependency>

 WebSecurityConfiguration:



/**
 * Spring Security 配置项
 */
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@EnableGlobalAuthentication
public class WebSecurityConfiguration {

    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Autowired
    private RestAuthenticationEntryPoint restAuthenticationEntryPoint;

    @Autowired
    private RestAccessDeniedHandler restAccessDeniedHandler;

    private UserDetailsService userDetailsService;

    @Autowired
    private ApplicationContext applicationContext;


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

        // 搜寻 匿名标记 url: PreAuthorize("hasAnyRole('anonymous')") 和 PreAuthorize("@tsp.check('anonymous')") 和 AnonymousAccess
        Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = applicationContext.getBean(RequestMappingHandlerMapping.class).getHandlerMethods();
        Set<String> anonymousUrls = new HashSet<>();
        for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {
            HandlerMethod handlerMethod = infoEntry.getValue();
            AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
            PreAuthorize preAuthorize = handlerMethod.getMethodAnnotation(PreAuthorize.class);
            PathPatternsRequestCondition pathPatternsCondition = infoEntry.getKey().getPathPatternsCondition();
            Set<String> patternList = new HashSet<>();
            if (null != pathPatternsCondition){
                Set<PathPattern> patterns = pathPatternsCondition.getPatterns();
                for (PathPattern pattern : patterns) {
                    patternList.add(pattern.getPatternString());
                }
            }
            if (null != preAuthorize && preAuthorize.value().toLowerCase().contains("anonymous")) {
                anonymousUrls.addAll(patternList);
            } else if (null != anonymousAccess && null == preAuthorize) {
                anonymousUrls.addAll(patternList);
            }
        }

        httpSecurity
                // 禁用basic明文验证
                .httpBasic(it -> it.disable())
                // 前后端分离架构不需要csrf保护
                .csrf(it -> it.disable())
                // 禁用默认登录页
                .formLogin(it -> it.disable())
                // 禁用默认登出页
                .logout(it -> it.disable())
                // 设置异常的EntryPoint,如果不设置,默认使用Http403ForbiddenEntryPoint
                .exceptionHandling(exceptions -> {
                    // 401
                    exceptions.authenticationEntryPoint(restAuthenticationEntryPoint);
                    // 403
                    exceptions.accessDeniedHandler(restAccessDeniedHandler);
                })
                // 前后端分离是无状态的,不需要session了,直接禁用。
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
                        // 允许匿名访问
                        .requestMatchers(anonymousUrls.toArray(new String[0])).permitAll()
                        // 允许所有OPTIONS请求
                        .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                        // 允许 SpringMVC 的默认错误地址匿名访问
                        .requestMatchers("/error").permitAll()
                        // 允许任意请求被已登录用户访问,不检查Authority
                        .anyRequest().authenticated())
                .authenticationProvider(authenticationProvider())
                // 加我们自定义的过滤器,替代UsernamePasswordAuthenticationFilter
                .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

        return httpSecurity.build();
    }



    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        // 允许所有域名进行跨域调用
        config.addAllowedOrigin("*");
        // 放行全部原始头信息
        config.addAllowedHeader("*");
        // 允许所有请求方法跨域调用
        config.addAllowedMethod("OPTIONS");
        config.addAllowedMethod("GET");
        config.addAllowedMethod("POST");
        config.addAllowedMethod("PUT");
        config.addAllowedMethod("DELETE");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);

    }
    @Bean
    public UserDetailsService userDetailsService() {
        return username -> userDetailsService.loadUserByUsername(username);
    }

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

    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService());
        // 设置密码编辑器
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }

}

JwtAuthenticationTokenFilter


/**
 * JWT登录授权过滤器
 */

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
                                    @NonNull FilterChain chain) throws ServletException, IOException {
        String authorization = request.getHeader("Authorization");
        response.setCharacterEncoding("utf-8");
        if (null == authorization){
            // 没有token
            chain.doFilter(request, response);
            return;
        }
        try{
            if (!authorization.startsWith("Bearer ")){
                // token格式不正确
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "token格式不正确");
                return;
            }
            boolean verify = MyJWTUtil.verify(authorization);
            if(!verify){
                // token格式不正确
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "token验证失败");
                return;
            }
        }catch (Exception e){
            // token格式不正确
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "token验证失败");
            return;
        }
        JWT jwt = MyJWTUtil.parseToken(authorization);
        Object uid = jwt.getPayload("uid");
        
        // todo 解析JWT获取用户信息
        LoginUser loginUser = new LoginUser();

        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser,null,null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        chain.doFilter(request, response);
    }

}
RestAuthenticationEntryPoint:



/**
 * 认证失败处理类
 */

@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Cache-Control", "no-cache");
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.getWriter().println(authException == null? "Unauthorized" : "需要先认证才能访问");
        response.getWriter().flush();

    }

}
RestAccessDeniedHandler:


/**
 * 自定义无权访问处理类
 */
@Component
public class RestAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
                       AccessDeniedException accessDeniedException) throws IOException {
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Cache-Control", "no-cache");
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        response.setStatus(HttpStatus.FORBIDDEN.value());
//        response.getWriter()
//                .println(accessDeniedException==null?"AccessDenied":accessDeniedException.getMessage());
        response.getWriter().println(accessDeniedException == null? "AccessDenied" : "没有访问权限");
        response.getWriter().flush();
    }

}
AnonymousAccess:


/**
 * 用于标记匿名访问方法
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnonymousAccess {

}
MyJWTUtil:


/**
 * JWT工具类
 */
public class MyJWTUtil extends JWTUtil {


    /**
     * 解析JWT Token
     *
     * @param token token
     * @return {@link JWT}
     */
    public static boolean verify(String token) {
        return verify(token, "LOGIN_TOKEN_KEY_20240410".getBytes());
    }

    /**
     * 解析JWT Token
     *
     * @param token token
     * @return {@link JWT}
     */
    public static boolean verify(String token, byte[] key) {
        if(StrUtil.isNotEmpty(token)){
            if(token.startsWith("Bearer ")){
                token = token.split("Bearer ")[1].trim();
            }
        }
        return JWT.of(token).setKey(key).verify();
    }

    /**
     * 解析JWT Token
     *
     * @param token token
     * @return {@link JWT}
     */
    public static JWT parseToken(String token) {
        if(StrUtil.isNotEmpty(token)){
            if(token.startsWith("Bearer ")){
                token = token.split("Bearer ")[1].trim();
            }
        }
        return JWT.of(token);
    }

    public static String getToken(HttpServletRequest request) {
        final String requestHeader = request.getHeader("Authorization");
        if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
            return requestHeader.substring(7);
        }
        return null;
    }

    public static String createToken(String userId) {
        Map<String, Object> payload = new HashMap<>(4);
        payload.put("uid", userId);
        payload.put("expire_time", System.currentTimeMillis() + 1000 * 60 * 60 * 8);
        return createToken(payload, "LOGIN_TOKEN_KEY_20240410".getBytes());
    }
}

DemoController:


@RestController
public class DemoController {


    @GetMapping("anonymous")
    @AnonymousAccess
    public String loadCondition() {
        return "anonymous";
    }

    @GetMapping("security")
    public String security() {
        return "security";
    }

}

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是Spring Boot 3 + Spring Security 6 + JWT的项目配置步骤: 1. 添加Spring Security和JWT的依赖 在pom.xml文件中添加以下依赖: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> ``` 2. 配置JWT 在application.yml或者application.properties文件中添加JWT的配置信息: ``` jwt: secret: yourSecretKey expiration: 604800000 # 7 days ``` 3. 配置Spring Security 创建一个继承自WebSecurityConfigurerAdapter的配置类,并添加以下代码: ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomUserDetailsService customUserDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/api/auth/**") .permitAll() .anyRequest() .authenticated(); http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public JwtAuthenticationFilter jwtAuthenticationFilter() { return new JwtAuthenticationFilter(); } } ``` 其中,CustomUserDetailsService是自定义的用户认证服务,JwtAuthenticationFilter是自定义的JWT认证过滤器。 4. 编写JWT认证过滤器 创建一个继承自OncePerRequestFilter的JwtAuthenticationFilter,并添加以下代码: ```java public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private JwtTokenProvider jwtTokenProvider; @Autowired private CustomUserDetailsService customUserDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { String token = jwtTokenProvider.resolveToken(request); if (token != null && jwtTokenProvider.validateToken(token)) { Authentication authentication = jwtTokenProvider.getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(authentication); } } catch (JwtAuthenticationException ex) { SecurityContextHolder.clearContext(); response.sendError(ex.getHttpStatus().value(), ex.getMessage()); return; } filterChain.doFilter(request, response); } } ``` 其中,JwtTokenProvider是自定义的JWT Token提供器。在这个过滤器中,我们通过JWT Token提供器解析请求中的Token,并将用户认证信息存储在SecurityContextHolder中。 5. 编写JWT Token提供器 创建一个JwtTokenProvider类,并添加以下代码: ```java @Service public class JwtTokenProvider { @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long expiration; public String createToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return Jwts.builder() .setClaims(claims) .setSubject(userDetails.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + expiration)) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } public boolean validateToken(String token) { try { Jwts.parser().setSigningKey(secret).parseClaimsJws(token); return true; } catch (JwtException | IllegalArgumentException ex) { throw new JwtAuthenticationException("Expired or invalid JWT token", HttpStatus.INTERNAL_SERVER_ERROR); } } public Authentication getAuthentication(String token) { UserDetails userDetails = customUserDetailsService.loadUserByUsername(getUsername(token)); return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); } public String getUsername(String token) { return Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody() .getSubject(); } public String resolveToken(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } } ``` 其中,UserDetails是Spring Security提供的用户认证信息对象,CustomUserDetailsService是自定义的用户认证服务。 在这个类中,我们使用JJWT库来创建和解析JWT Token,并在getAuthentication方法中从Token中获取用户认证信息,并将其封装成Spring Security的Authentication对象。 以上就是Spring Boot 3 + Spring Security 6 + JWT的项目配置步骤。希望能够帮到您!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值