一、先看下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);
}
}
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"));
}
}
}
在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;
}
}
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);
}
}
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());
}
}
来源:oschina
链接:https://my.oschina.net/u/4377109/blog/4289899