【知识库系统】JWT实现前后端分离验证
1. SpringSecurity 默认的认证是需要通过 UsernamePasswordAuthenticationFilter 进行认证的,该过滤器认证前,会到 SecurityContextHolder 中寻找是否有符合的 Authentication 认证信息,如果有已认证的信息,则 UsernamePasswordAuthenticationFilter 可以直接拿到认证信息,进行后面的
本文会先从理论和实践两部分讲述如何去理解和实现通过JWT进行身份认证。
一、理论
1. SpringSecurity 默认的认证是需要通过 UsernamePasswordAuthenticationFilter 进行认证的,该过滤器认证前,会到 SecurityContextHolder 中寻找是否有符合的 Authentication 认证信息,如果有已认证的信息,则 UsernamePasswordAuthenticationFilter 可以直接拿到认证信息,进行后面的授权操作。

2. SecurityFilterChain 中允许我们使用 addFilterBefore 在某个过滤器前面添加自定义过滤器,所以我们可以自定义一个 JwtFilter 对用户信息进行授权认证,认证成功后将授权信息写入 SecurityContextHolder,这样在后面的 UsernamePasswordAuthenticationFilter 过滤器就能直接拿到授权信息了。

根据以上两点,最后能够实现先进行 JWT 验证,通过则授权;不通过则进行 UsernamePassword 验证。

3. 最后要补充一句,在数据库里面保存的权限,一定要以ROLE_开头,比如“ROLE_ADMIN”,只是“ADMIN”的话,最后 SpringSecurity 也会给你返回一个 403 错误。
二、实践
1. 首先引入 JWT
<!-- jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.0.0</version>
</dependency>
2. 编写 JwtUtil 工具,用于加密和解密 token 信息
public class JwtUtil {
private static final String secret = "secret";
public static String createToken(String username, String authentications) {
long expire = 7 * 24 * 3600 * 1000;
return JWT.create().withAudience(username)
.withIssuedAt(new Date())
.withExpiresAt(new Date(System.currentTimeMillis() + expire))
.withClaim("authentications", authentications)
.sign(Algorithm.HMAC256(username + secret));
}
public static DecodedJWT decodedJWT(String token, String username) {
DecodedJWT jwt = null;
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(username + secret)).build();
jwt = verifier.verify(token);
return jwt;
}
public static boolean verifyToken(String token, String username) {
DecodedJWT jwt = null;
try {
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(username + secret)).build();
jwt = verifier.verify(token);
return true;
} catch (Exception e) {
System.out.println(username + " 验证失败");
}
return false;
}
}
3. 实现 AuthticationSuccessHandler ,用于用户在第一次通过 UsernamePassword 验证的时候,返回一个 token
public class SuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
UsernamePasswordAuthenticationToken user = (UsernamePasswordAuthenticationToken) authentication;
String token = JwtUtil.createToken(user.getName(), user.getAuthorities().toString());
response.setContentType("text/json;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(token);
writer.flush();
writer.close();
}
}
4. 创建一个 JwtFilter ,主要是实现验证 token 授权信息并将授权信息写入 SecurityContextHolder 中
@Component
public class JwtFilter extends OncePerRequestFilter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
UserMapper userMapper;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = "";
String username = "";
for (Cookie cookie : request.getCookies()) {
if (cookie.getName().equals("username")) {
username = cookie.getValue();
}
if (cookie.getName().equals("token")) {
token = cookie.getValue();
}
}
if (JwtUtil.verifyToken(token, username)) {
DecodedJWT jwt = JwtUtil.decodedJWT(token, username);
String authentications = jwt.getClaims().get("authentications").toString();
String authentication = authentications.substring(2, authentications.length()-2);
User user = new User(username, "", AuthorityUtils.createAuthorityList(authentication));
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
} else {
System.out.println(username + "JwtToken验证失败");
}
filterChain.doFilter(request, response);
}
}
5. 将 SuccessHandler 和 JwtFilter 配置到 SecurityFilterChain 中才能生效。
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private JwtFilter jwtFilter;
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/admin").hasRole("ADMIN")
.requestMatchers("/user/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(login -> login
.loginPage("/login")
.successHandler(new SuccessHandler())
.permitAll()
)
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
.csrf(AbstractHttpConfigurer::disable);
return http.build();
}
}
6. 最后测试一下,发送一个登录请求,返回 token 信息,

然后将 token 放在cookie 里面再请求一个需要权限才能访问的页面,正常返回

否则会重定向到登录页面

更多推荐



所有评论(0)