一、先看下DaoAuthenticationProvider的认证过程
1、从读取用户名和密码开始的身份验证Filter将一个UsernamePasswordAuthenticationToken传递给由ProviderManager实现的AuthenticationManager。
2、ProviderManager被配置为使用DaoAuthenticationProvider类型的AuthenticationProvider。
3、第三个DaoAuthenticationProvider从UserDetailsService中查找用户详细信息。
4、DaoAuthenticationProvider使用PasswordEncoder验证上一步返回的用户详细信息上的密码。
5、当身份验证成功时,返回的身份验证类型为UsernamePasswordAuthenticationToken,其主体是已配置的UserDetailsService返回的用户详细信息。最终,返回的UsernamePasswordAuthenticationToken将由身份验证过滤器在securitycontext中设置。
二、过程分析
1)登陆认证
项目中,我们如何实现这个过程
1、自定义UserDetailsService(用于提供用户信息一般通过用户名从数据库查询)
/** * 用户登录认证信息查询 */ @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private TUserDAO tUserDAO; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { TUser user = tUserDAO.getByUserName(username); if (user == null) { throw new UsernameNotFoundException("该用户不存在"); } // 用户权限列表,根据用户拥有的权限标识与如 @PreAuthorize("hasAuthority('sys:menu:view')") 标注的接口对比,决定是否可以调用接口 // Set<String> permissions = sysUserService.findPermissions(user.getName()); Set<String> permissions = new HashSet<>(); permissions.add("query"); List<GrantedAuthority> grantedAuthorities = permissions.stream().map(GrantedAuthorityImpl::new).collect(Collectors.toList()); return new JwtUserDetails(user.getName(), user.getPassword(), user.getSalt(), user.getAccountType(), grantedAuthorities); } }View Code
2、自定义DaoAuthenticationProvider (用于验证用户密码是否正确)
/** * 身份验证提供者 */ public class JwtAuthenticationProvider extends DaoAuthenticationProvider { public JwtAuthenticationProvider(UserDetailsService userDetailsService) { setUserDetailsService(userDetailsService); } @Override protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { if (authentication.getCredentials() == null) { logger.debug("Authentication failed: no credentials provided"); throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } Integer accountType = ((JwtUserDetails) userDetails).getAccountType(); String presentedPassword = authentication.getCredentials().toString(); String salt = ((JwtUserDetails) userDetails).getSalt(); if (!new PasswordEncoder(salt).matches(userDetails.getPassword(), presentedPassword)) { logger.debug("Authentication failed: password does not match stored value"); throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } } }View Code
在spring security中,将用户的获取和密码的验证过程拆开了
代码实现:
@Autowired
private AuthenticationManager authenticationManager;
public HttpResult login(@RequestBody LoginForm loginForm, HttpServletRequest request) throws IOException { // 1、这一步主要获取用户密码 TUserDO tUser = tUserService.login(loginForm.getUsername()); // 2、进行spring security 认证 JwtAuthenticatioToken token = new JwtAuthenticatioToken(loginForm.getUsername(),loginForm.getPassword()); token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); // 执行登录认证过程 Authentication authentication = authenticationManager.authenticate(token); // 认证成功存储认证信息到上下文 SecurityContextHolder.getContext().setAuthentication(authentication); // 生成令牌并返回给客户端 token.setToken(JwtTokenUtils.generateToken(authentication)); return HttpResult.success(token); }
JwtAuthenticatioToken实现了UsernamePasswordAuthenticationToken,主要封装了JWT token
/** * 自定义令牌对象 */ public class JwtAuthenticatioToken extends UsernamePasswordAuthenticationToken { private static final long serialVersionUID = 1L; private String token; public JwtAuthenticatioToken(Object principal, Object credentials){ super(principal, credentials); } public JwtAuthenticatioToken(Object principal, Object credentials, String token){ super(principal, credentials); this.token = token; } public JwtAuthenticatioToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities, String token) { super(principal, credentials, authorities); this.token = token; } public String getToken() { return token; } public void setToken(String token) { this.token = token; } public static long getSerialversionuid() { return serialVersionUID; } }View Code
2)接口拦截认证
1、配置分析
其实我们只需要明确一点,spring security功过容器的filter扩展机制实现的,只不过它定义了一个DelegatingFilterProxy这个filter(这个里面有引用了FiltrChainProxy,通过它可以调用security中一系列的filter),那么我们就是通过扩展或实现security中的filter来满足我们一定的需求
2、配置拦截规则
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override public void configure(AuthenticationManagerBuilder auth) { // 使用自定义身份验证组件 auth.authenticationProvider(new JwtAuthenticationProvider(userDetailsService)); } @Override protected void configure(HttpSecurity http) throws Exception { // 禁用 csrf, 由于使用的是JWT,我们这里不需要csrf http.exceptionHandling().authenticationEntryPoint(new JwtUnauthorizedEntryPoint()).and() .cors().and().csrf().disable() //.addFilterBefore(new JwtAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class) .authorizeRequests() // 跨域预检请求 .antMatchers("/login").permitAll() .antMatchers("/logout").permitAll() .antMatchers(HttpMethod.OPTIONS, "/**").permitAll(); // 其他所有请求需要身份认证 http.authorizeRequests().anyRequest().authenticated(); // 退出登录处理器 http.logout().logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()); // token验证过滤器 http.addFilterBefore(new JwtAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class); } @Bean @Override public AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); } }
JwtAuthenticationFilter
/** * 登录认证过滤器 * */ public class JwtAuthenticationFilter extends BasicAuthenticationFilter { private static final Set<String> ALLOWED_PATHS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("/login", "/register", "/actuator/health"))); @Autowired public JwtAuthenticationFilter(AuthenticationManager authenticationManager) { super(authenticationManager); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { String path = request.getRequestURI().substring(request.getContextPath().length()).replaceAll("[/]+$", ""); boolean allowedPath = ALLOWED_PATHS.contains(path); // todo 该判断没用 if (!allowedPath) { // 获取token, 并检查登录状态 logger.debug(request.getRequestURI()); // 构造了一个authentication,写进了spring request中 try { SecurityUtils.checkAuthentication(request); } catch (BadCredentialsException badCredentialsException) { // token错误则401 response.sendError(HttpServletResponse.SC_UNAUTHORIZED, badCredentialsException.getMessage()); return; } } chain.doFilter(request, response); } }View Code
JwtUnauthorizedEntryPoint
public class JwtUnauthorizedEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.sendError(HttpServletResponse.SC_UNAUTHORIZED,authException.getMessage()); } }三、后续 时间问题,这块后续会再完善下博客内容