Spring Security 5.7.5 CURRENT GA-JWT无状态登录

一、实现思路

  1. 用户名/密码登录,成功后返回给前端token,可以保存在cookie/localstorage中,后续的请求都需要带上这个token(放在cookie或者请求头)。
  2. 注意XSS和CSRF攻击。

二、 实战

一、JWT工具类

public class JwtUtils {
    private static SecretKey secretKey = Keys.hmacShaKeyFor(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8));

    /**
     * 生成jwt
     * @param userDetails
     */
    public static String compact(UserDetails userDetails) {
        return Jwts.builder()
                .signWith(secretKey)
                .setExpiration(new Date(System.currentTimeMillis() + Duration.ofMinutes(1).toMillis()))
                .claim("userId", userDetails.getUsername())
                .claim("authority", userDetails.getAuthorities())
                .compact();
    }

    /**
     * 解析jwt
     * @param authrozation
     */
    public static Jws<Claims> parse(String authrozation) throws ExpiredJwtException, SignatureException{
        try{
            return Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(authrozation);
        } catch (Exception e) {
            throw e;
        }
    }
}

二、JSON格式登录过滤器

  • 本例子是存储在cookie中。
public class JsonAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    	// 1. 判断是否为POST请求
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        // 2. 将用户的请求信息包装成map集合
        Map<String, Object> map = null;
        try {
            map = new ObjectMapper().readValue(request.getInputStream(), Map.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        
        // 3. 获取用户输入的用户名和密码
        String username = ((String) map.get("username")).trim();
        String password = ((String) map.get("password")).trim();
        // 4. 包装成UsernamePasswordAuthenticationToken
        UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
                password);
        // 5. 设置Details,用于后续的认证
        setDetails(request, authRequest);
		// 6. 让父类UsernamePasswordAuthentiocationFilter来进行认证操作
        return this.getAuthenticationManager().authenticate(authRequest);
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        // 若认证成功则生成jwt返回给前端,并且存储在cookie中,当然也可以是localstorage中
        
        // 1. 获取认证成功的对象
        UserDetails userDetails = (UserDetails) authResult.getPrincipal();
        // 2. 生成token
        String token = JwtUtils.compact(userDetails);
        // 3. 存储在cookie中,httponly可以防止XSS攻击,sameSite可以防止CSR攻击F,具体作用自行搜索
        ResponseCookie cookie = ResponseCookie.fromClientResponse("accessToken", token).httpOnly(true).sameSite("Strict").build();
        response.setHeader(HttpHeaders.SET_COOKIE, cookie.toString());

		// 4. 响应认证成功的信息,可以用自己的工具类,也可以调用自己定义的成功处理器
        ResponseUtils.responseJson(response, JsonResult.success("登录成功!", token));
        // this.successHandler.onAuthenticationSuccess(request, response, authResult);
    }
}

三、JWT鉴权过滤器

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private static final String LOGIN_URL = "/login";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 1. 如果是/login,说明登录操作,直接跳过让登录过滤器工作
        if (LOGIN_URL.equals(request.getRequestURI())) {
            filterChain.doFilter(request, response);
            return;
        }

		// 2. 获取cookie中的token信息
        String authorization = null;
        Cookie[] cookies = request.getCookies();
        if (Objects.isNull(cookies)) {
            filterChain.doFilter(request, response);
            return;
        }
        for (Cookie cookie : cookies) {
            if ("accessToken".equals(cookie.getName())) {
                authorization = cookie.getValue();
            }
        }

		/ 3. 将获取到的token信息进行解析,若抛出异常,说明该token不合法或者过期或者其他,可以自己进行异常捕获然后处理一下
        Jws<Claims> jws;
        try {
            jws = JwtUtils.parse(authorization);
        } catch (Exception e) {
            filterChain.doFilter(request, response);
            return;
        }

		// 4. 获取权限信息
        List list = (List) jws.getBody().get("authority");
        List<Object> roles = new ArrayList<>();
        for (Object o : list) {
            roles.add(((Map<?, ?>) o).get("authority"));
        }

		// 5. 将获取的权限信息和用户名用于生成一个 “认证成功” 的Authentication对象(必须使用三个参数)
		// 最后将这样的认证成功对象给SecurityContext,,这样下一个请求进来就能知道是否已认证过(由授权过滤器他们操作)
		// 为什么这样?详见Security的认证流程,最后会调用AbstractUserDetailsAuthenticationProvider的createSuccessAuthentication
		/**
        this.principal = principal;
        this.credentials = credentials;
        this.setAuthenticated(false);  // 两个参数设置为false

        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true)  // 三个参数才会设置true
		*/
        SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(jws.getBody().get("userId"), null, roles.stream().map(r -> new SimpleGrantedAuthority((String) r)).collect(Collectors.toList())));
        filterChain.doFilter(request, response);
    }
}

四、配置类

@Configuration
public class SecurityConfig {
	// 1. 用来获取GlobalAuthenticationManager
    @Autowired
    AuthenticationConfiguration authenticationConfiguration;

    JsonAuthenticationFilter jsonAuthenticationFilter() throws Exception {
        JsonAuthenticationFilter jsonAuthenticationFilter = new JsonAuthenticationFilter();
        jsonAuthenticationFilter.setAuthenticationManager(authenticationConfiguration.getAuthenticationManager());
        return jsonAuthenticationFilter;
    }

    @Bean
    DefaultSecurityFilterChain filterChain(HttpSecurity http,
                                           JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception {
        // 设置两个过滤器,Jwt的在前面,JSON的在后面就行,因为以后每次请求必须要先经过JWt的过滤器。
        return http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterAt(jsonAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .build();
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cs4m

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值