SpringBoot3配置SpringSecurity6

访问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";
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值