前言
1、会基本的Spring Security配置
2、大概了解Spring Security原理(一条过滤器链)
简单理解
Spring Security就是一条过滤器链,如果你登录了,那么会有过滤器将你的认证信息解析出来并放到Security的上下文中,这样其他过滤器就通过这个认证信息来鉴权
JWT配置
之前由Security的过滤器来解析认证信息,现在我们自己定义一个过滤器,将携带的token解析为认证信息即可
// cn.mb.itemdemo.controller.TestController
@GetMapping("/login")
public CommonResult login() {
// 登录校验
UserDetails userDetails = userService.loadUserByUsername("root");
String token = jwtTokenUtil.generateToken(userDetails);
return CommonResult.success(token);
}
// cn.mb.itemdemo.component.CustomTokenFilter
public class CustomTokenFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private UserService userService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
// 获取请求头
String header = request.getHeader(jwtTokenUtil.getTokenHeader());
// 解析token
if (header != null && header.startsWith(jwtTokenUtil.getTokenHead())) {
String token = header.substring(jwtTokenUtil.getTokenHead().length());
// 获取用户名
String username = jwtTokenUtil.getUserNameFromToken(token);
// 每次都重新查询用户及其权限(保证动态权限)
UserDetails userDetails = userService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(token, userDetails)) {
// 将用户信息放入SecurityContextHolder中
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
}
protected void configure(HttpSecurity http) throws Exception {
// 自定义token解析器
http.addFilterBefore(customTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
通过上述操作,即可实现JWT认证
其实只要知道token是在过滤链中解析成用户数据即可,后面的事就交给Security来做(一开始我也是懵的不知道怎么结合,其实跟用拦截器判断认证一样)
鉴权
FilterSecurityInterceptor是过滤器链的最后一个,在执行时就会做鉴权操作
但其依赖AccessDecisionManager.decide方法做实际鉴权操作
且依赖SecurityMetadataSource.getAttributes获取当前资源对应的权限
因此我们需要自定义上述三个对象并注入到Security中
public class CustomAuthFilter extends AbstractSecurityInterceptor implements Filter {
private final IgnoreUrlsConfig ignoreUrlsConfig;
private final CustomMetadataSource customMetadataSource;
public CustomAuthFilter(IgnoreUrlsConfig ignoreUrlsConfig, CustomMetadataSource customMetadataSource, CustomAccessDecisionManager customAccessDecisionManager) {
this.ignoreUrlsConfig = ignoreUrlsConfig;
this.customMetadataSource = customMetadataSource;
super.setAccessDecisionManager(customAccessDecisionManager);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
// OPTIONS请求直接放行
if (request.getMethod().equals(HttpMethod.OPTIONS.toString())) {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
return;
}
// 白名单请求直接放行
PathMatcher pathMatcher = new AntPathMatcher();
for (String path : ignoreUrlsConfig.getUrls()) {
if (pathMatcher.match(path, request.getRequestURI())) {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
return;
}
}
// 此处会调用AccessDecisionManager中的decide方法进行鉴权操作
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return customMetadataSource;
}
}
public class CustomAccessDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
// 当接口未被配置资源时直接放行
if (CollUtil.isEmpty(configAttributes)) {
return;
}
Iterator<ConfigAttribute> iterator = configAttributes.iterator();
while (iterator.hasNext()) {
ConfigAttribute configAttribute = iterator.next();
// 将当前访问所需资源或用户拥有资源进行比对
String needAuthority = configAttribute.getAttribute();
for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
// 如果有该权限直接放行
if (needAuthority.trim().equals(grantedAuthority.getAuthority())) {
return;
}
}
}
throw new AccessDeniedException("抱歉,您没有访问权限");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
return true;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
public class CustomMetadataSource implements FilterInvocationSecurityMetadataSource {
// 所有资源
private List<String> allResource;
@PostConstruct
public void loadDataSource() {
// 把所有权限加载到内存中
allResource = new ArrayList<>();
allResource.add("/add");
allResource.add("/delete");
allResource.add("/update");
}
@Override
public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
List<ConfigAttribute> configAttributes = new ArrayList<>();
// 获取当前访问的路径
String url = ((FilterInvocation) o).getRequestUrl();
if (allResource.contains(url)) {
configAttributes.add(new org.springframework.security.access.SecurityConfig(url));
}
return configAttributes;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
protected void configure(HttpSecurity http) throws Exception {
// 自定义权限过滤器
http.addFilterBefore(customAuthFilter, FilterSecurityInterceptor.class);
}
小结
以上就是SpringBoot + Spring Security + JWT的认证授权过程了
一开始看还很懵他都是自己内置的页面,那前后分离怎么办
其实就像原本用拦截器做token校验一样,加个token过滤器解析成Security需要的认证信息即可,后面框架会来认证
而鉴权方面,则像上面一样做即可(PS:但我感觉直接在过滤器里面都可以完成鉴权操作了,只不过代码会有点长)
demo
2021-02-25 鉴权操作
其实可以直接实现AccessDecisionManager并在配置类中注入,不用像上面那么麻烦再去实现过滤器,代码如下
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().accessDecisionManager(myAccessDecisionManager);
}
public class CustomAccessDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
String url = ((FilterInvocation) o).getRequestUrl();
for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
// 如果有该权限直接放行
if (url.trim().equals(grantedAuthority.getAuthority())) {
return;
}
}
throw new AccessDeniedException("抱歉,您没有访问权限");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
return true;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}