一、实现思路
- 用户名/密码登录,成功后返回给前端token,可以保存在cookie/localstorage中,后续的请求都需要带上这个token(放在cookie或者请求头)。
- 注意XSS和CSRF攻击。
二、 实战
一、JWT工具类
public class JwtUtils {
private static SecretKey secretKey = Keys.hmacShaKeyFor(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8));
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();
}
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格式登录过滤器
public class JsonAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
Map<String, Object> map = null;
try {
map = new ObjectMapper().readValue(request.getInputStream(), Map.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
String username = ((String) map.get("username")).trim();
String password = ((String) map.get("password")).trim();
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
password);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
UserDetails userDetails = (UserDetails) authResult.getPrincipal();
String token = JwtUtils.compact(userDetails);
ResponseCookie cookie = ResponseCookie.fromClientResponse("accessToken", token).httpOnly(true).sameSite("Strict").build();
response.setHeader(HttpHeaders.SET_COOKIE, cookie.toString());
ResponseUtils.responseJson(response, JsonResult.success("登录成功!", token));
}
}
三、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 {
if (LOGIN_URL.equals(request.getRequestURI())) {
filterChain.doFilter(request, response);
return;
}
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;
}
List list = (List) jws.getBody().get("authority");
List<Object> roles = new ArrayList<>();
for (Object o : list) {
roles.add(((Map<?, ?>) o).get("authority"));
}
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 {
@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 {
return http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterAt(jsonAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.build();
}
}