这篇博文主要分析了下面这个博客中的例子:
www.yangbuyi.top/archives/%E…
上面这个博客中的例子:
SecurityContextHolder.getContext()获取安全上下文对象, 就是那个保存在 ThreadLocal 里面的安全上下文对象
jwt解决用户认证问题,有没有带token访问
spring-security解决用户是否有权限,角色访问特性url
首先看一下Security配置
@Override
protected void configure(HttpSecurity http) throws Exception {
// 自定义登录校验过滤器一定要在UsernamePasswordAuthenticationFilter 执行之前 执行
http.addFilterBefore(jwtCheckFilter, UsernamePasswordAuthenticationFilter.class);
// 关闭csrf攻击
http.csrf().disable();
// 登录配置
http.formLogin()
.loginProcessingUrl("/doLogin") // 指定登录地址
.successHandler(authenticationSuccessHandler()) // 登录成功执行的
.failureHandler(authenticationFailureHandler()) // 登录失败执行的
.permitAll();
// 403 权限不足
http.exceptionHandling().accessDeniedHandler(accessDecision);
// 基于token方式 不会存储session来进行登录判断了
http.sessionManagement().disable();
// 集成JWT 全部放行接口 使用jwt过滤器来进行 鉴权
http.authorizeRequests().antMatchers("/**").permitAll();
}
可以看到,配置里所有接口都是放行的,jwtCheckFilter的作用是验证token。/doLogin是不需要认证token的。
登录流程:
1,/doLogin,首先会执行jwtCheckFilter,下面的代码可以看到jwtCheckFilter过滤器是直接放行/doLogin接口的。
if ("/doLogin".equals(requestURI) && "POST".equalsIgnoreCase(method)) {
filterChain.doFilter(httpServletRequest, httpServletResponse);
return;
}
2,再会调用 UserDetailsServiceImpl 类中的 loadUserByUsername 方法,该方法会根据用户名(/doLogin接口携带的username参数),获取到用户的详细信息(一般从数据库中获取),该详细信息包括用户密码,用户所拥有的权限(从用户权限表中查询)。loadUserByUsername方法返回 UserDetails 对象,该类是Spring Security自带的类。为什么会走到这个方法中,这个也是在Security配置文件中配置的:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
3,Spring Security自带框架会验证/doLogin接口携带的用户名和密码是否正确。
4,如果不正确,则会执行配置文件中配置的登录失败回调 AuthenticationFailureHandler,返回给前端登录失败的信息
@Bean
public AuthenticationFailureHandler authenticationFailureHandler() {
return (request, response, exception) -> {
response.setContentType("application/json;charset=utf-8");
// 写出去
HashMap<String, Object> map = new HashMap<>(4);
map.put("code", 401);
map.put("msg", "登陆失败");
ObjectMapper objectMapper = new ObjectMapper();
String s = objectMapper.writeValueAsString(map);
PrintWriter writer = response.getWriter();
writer.write(s);
writer.flush();
writer.close();
};
}
5,正确,则会执行配置文件中配置的登录成功回调 AuthenticationSuccessHandler,在这个成功回调中,使用jwt类库生成jwt token返回给前端,随后需要携带token的接口,前端都需要携带这个token。
UserDetails principal = (UserDetails) authentication.getPrincipal();
上面的代码可以看到,在第2步中返回的UserDetails对象在用户名密码验证成功了,会存入到Authentication,将Authentication存入到SecurityContext,这样就可以在当前线程中类似于ThreadLocal一样获取到用户信息UserDetails。
登录后的接口请求流程:
1,登录成功后的其他接口都需要携带token参数,这个是在jwtCheckFilter过滤器中验证的。Token在这个项目中是存到redis中的。
2,验证通过后,将用户信息包括其拥有的权限存到SecurityContextHolder上下文中,
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, null, simpleGrantedAuthorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
3,上述的token验证通过后,Spring Security框架则会通过第2步中得到的用户及其拥有的权限去判断该用户是否拥有访问当前url的权限,如果没有权限则会走到:
@Component
public class AccessDecision implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=UTF-8");
//按照系统自定义结构返回授权失败
HashMap<String, Object> stringObjectHashMap = new HashMap<>(4);
stringObjectHashMap.put("code", 403);
stringObjectHashMap.put("msg", "权限不足");
ObjectMapper objectMapper = new ObjectMapper();
String s = objectMapper.writeValueAsString(stringObjectHashMap);
PrintWriter writer = httpServletResponse.getWriter();
writer.write(s);
writer.flush();
writer.close();
}
}
中,AccessDeniedHandler也是在Security配置文件中配置的。返回给前端权限不足的提示。
4,如果用户拥有该权限,则会走到对应的controller方法中。
注意:匿名用户就是没有登录的用户,匿名用户当然就没有角色和权限,则无权访问使用@PreAuthorize注解的controller接口。当然上面这个博客例子并没有使用到authenticationEntryPoint,而是把匿名用户的验证都交给了jwtCheckFilter
//AuthenticationEntryPoint 用来解决匿名用户访问无权限资源时的异常
//AccessDeineHandler 用来解决认证过的用户访问无权限资源时的异常
.authenticationEntryPoint(authenticationErrorHandler)
.accessDeniedHandler(jwtAccessDeniedHandler)
参考:
1,www.cnblogs.com/RudeCrab/p/… 这个博客写的非常好
2,blog.csdn.net/xiaokanfuch…
3, blog.csdn.net/z69183787/a… 登录名密码什么时候验证,源码分析