认证部分可以看具体笔记

图中只展示了核心过滤器,其它的非核心过滤器并没有在图中展示。
UsernamePasswordAuthenticationFilter
:负责处理我们在登陆页面填写了用户名密码后的登陆请求。入门案例的认证工作主要有它负责。【判断你的用户名和密码是否正确】
ExceptionTranslationFilter
:处理过滤器链中抛出的任何AccessDeniedException
和AuthenticationException
。【处理认证授权过程中的所有异常,方便统一处理】
FilterSecurityInterceptor
:负责权限校验的过滤器。【它会判断你登录成功的用户是“谁”,“你”具有什么权限,当前访问的资源需要什么权限】
过程详解:
当前端提交用户名和密码过来时,进入了UsernamePasswordAuthenticationFilter
过滤器。
彩色字体的类均是比较重要的接口,在实现认证的过程中均需要自定义一个类来重新实现或者变更为Spring中其他实现类。
概念速查:
Authentication
接口: 它的实现类,表示当前访问系统的用户,封装了用户的权限等相关信息。
AuthenticationManager
接口:定义了认证Authentication的方法 ,实现类是ProviderManager
- 它的实现类是**
ProviderManager
,它的功能主要是实现认证用户,因为在写登录接口时,可以通过配置类的方式,注入Spring容器中来使用它的authenticate
方法**。
UserDetailsService
接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。
- 原本的实现类是**
InMemoryUserDetailsManager
**,它是在内存中查询,因为我们需要自定义改接口。
- **
UserDetails
接口:提供核心用户信息。通过UserDetailsService
根据用户名获取处理的用户信息要封装成UserDetails
对象返回。然后将这些信息封装到Authentication
**对象中。
- 当我们自定义**
UserDetailsService
接口时,需要我们定义一个实体类来实现这个接口来供UserDetailsService
**接口返回。【注意是实体类】
基于Cookie与Session的方案
- 优点:实现简单,成熟
- 缺点:集群情况下实现困难,反向代理配置繁琐,移动端不友好
基于jwt的方案
- 优点:无状态,服务器资源占用小,天生支持分布式,实现简单
- 缺点:安全性不高,jwt没办法实现过期将用户踢下线,只能前端实现,但是jwt在有效期内依然有效。
基于自定义Token+Redis
- 优点:服务器可以实现对用户的下线等操作,支持分布式
- 缺点:相比jwt是中心化方案,对服务器存储有要求,分布式需要用到Redis
技术路线
- 可以使用controller,然后不使用formlogin就不会注入UserPasswordAuthenticationFilter(比较灵活,实现拓展功能比较简单)
- 也可以重写UserPasswordAuthenticationFilter,然后重写它的认证成功,失败接口
鉴权
鉴权发生在认证之后,因此鉴权入口在FilterSecurityInterceptor中,原理不多赘述,有三种实现方法。
- 直接重写一个
AccessDecisionManager
,将它用作默认的AccessDecisionManager
,并在里面直接写好鉴权逻辑。
- 再比如重写一个投票器,将它放到默认的
AccessDecisionManager
里面,和之前一样用投票器鉴权。
- 我看网上还有些博客直接去做
FilterSecurityInterceptor
的改动。
用户权限
UserUserDetails 用户权限
GrantedAuthority 一般使用实现类SimpleGrantedAuthority就够了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
@Override @JsonDeserialize(using = CustomAuthorityDeserializer.class) public Collection<? extends GrantedAuthority> getAuthorities() { List<SimpleGrantedAuthority> authorities = roles .stream() .map(role -> new SimpleGrantedAuthority(role.getName())) .collect(Collectors.toList());
return authorities; }
|
鉴权规则源
FilterInvocationSecurityMetadataSource 鉴权规则源
用于返回当前API所需的ROLE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
|
@Component public class CustomFilter implements FilterInvocationSecurityMetadataSource {
@Autowired private IMenuService menuService;
AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException { String requestUrl = ((FilterInvocation) o).getRequestUrl(); List<Menu> menus = menuService.getMenusWithRole(); for (Menu menu : menus) { if (antPathMatcher.match(menu.getUrl(),requestUrl)) { String[] str = menu.getRoles().stream().map(Role::getName).toArray(String[]::new); return SecurityConfig.createList(str); } } return SecurityConfig.createList("ROLE_LOGIN"); }
@Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; }
@Override public boolean supports(Class<?> aClass) { return false; } }
|
授权管理
AccessDecisionManager
用户GrantedAuthority与ConfigAttribute一对比就知道用户有没有权限访问该api了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
@Override public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { for (ConfigAttribute attribute : configAttributes) { String needRole = attribute.getAttribute(); Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); for (GrantedAuthority authority : authorities) { if (authority.getAuthority().equals(needRole)) { return; } } if ("ROLE_LOGIN".equals(needRole)) { if (authentication instanceof AnonymousAuthenticationToken) { throw new AccessDeniedException("尚未登录,请登录"); } else { return; } } } throw new AccessDeniedException("权限不足,请联系管理员"); }
|
授权错误处理器
AccessDeniedHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
@Component public class RestfulAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException { httpServletResponse.setCharacterEncoding("UTF-8"); httpServletResponse.setContentType("application/json"); PrintWriter out = httpServletResponse.getWriter(); RespBean bean = RespBean.error("权限不足,请联系管理员!"); bean.setCode(403); out.write(new ObjectMapper().writeValueAsString(bean)); out.flush(); out.close(); } }
|
配置授权过滤器
OncePerRequestFilter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| package com.disda.cowork.config.security.components;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Value("${jwt.tokenHeader}") private String tokenHeader;
@Value("${jwt.tokenHead}") private String tokenHead;
@Autowired private JwtTokenUtil jwtTokenUtil;
@Autowired private UserDetailsService userDetailsService;
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader(tokenHeader); if(authHeader!=null&&authHeader.startsWith(tokenHead)){ String authToken = authHeader.substring(tokenHead.length()); String username = jwtTokenUtil.getUserNameFromToken(authToken); if(username!=null && SecurityContextHolder.getContext().getAuthentication() == null){ UserDetails userDetails = userDetailsService.loadUserByUsername(username); if(jwtTokenUtil.validateToken(authToken,userDetails)){ UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } } } filterChain.doFilter(request,response); } }
|
配置Spring Security
SecurityConfig extends WebSecurityConfigurerAdapter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .anyRequest().authenticated() .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { @Override public <O extends FilterSecurityInterceptor> O postProcess(O object) { object.setAccessDecisionManager(customUrlDecisionManager); object.setSecurityMetadataSource(customFilter); return object; } }) .and() .headers() .cacheControl();
http.addFilterBefore(jwtAuthencationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
http.exceptionHandling() .accessDeniedHandler(restfulAccessDeniedHandler) .authenticationEntryPoint(restAuthorizationEntryPoint);
}
|