前言
最近在整合security和jwt作为基础项目jar包,学无止境头发日渐稀疏
流程介绍
- 前端登录页面:输入用户名和密码 post 登录,其中密码用rsa 公钥加密
- 后端登录接口:
- rsa私钥解密,获取密码明文
- 实例化 UsernamePasswordAuthenticationToken 对象用于密码校验
-调用 AbstractUserDetailsAuthenticationProvider.authenticate方法用于校验,对应方法在org.springframework.security.authentication.dao中(已精简)
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//从authentication中获取用户名
String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
boolean cacheWasUsed = true;
//缓存???好像是没有起作用,原因是没有配诸如 Ehcache
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
//这里获取用户信息 参考下一段代码
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
} catch (UsernameNotFoundException var6) {
}
}
try {
this.preAuthenticationChecks.check(user);
//校验密码
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
} catch (AuthenticationException var7) {
}
return this.createSuccessAuthentication(principalToReturn, authentication, user);
}
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
this.prepareTimingAttackProtection();
try {
//这里就是为什么要重写loadUserByUsername方法的原因
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
} else {
return loadedUser;
}
} catch (UsernameNotFoundException var4) {
this.mitigateAgainstTimingAttack(authentication);
throw var4;
} catch (InternalAuthenticationServiceException var5) {
throw var5;
} catch (Exception var6) {
throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
}
}
//检验密码
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
} else {
String presentedPassword = authentication.getCredentials().toString();
//passwordEncoder解码比较明文,其中 presentedPassword是明文,userDetails.getPassword()后者是数据库中获取的密文
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
this.logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
}
- 密码校验成功后,将授权认证信息放入到上下文中,同时根据认证信息生成令牌(JWT)
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
String token = tokenProvider.createToken(authentication);
- 前端获取返回的token作为往后接口的Header中Authorization的值,完成整套授权认证
附SecurityConfig文件配置
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private TokenProvider tokenProvider;
@Resource
private CorsFilter corsFilter;
@Resource
private JwtAuthenticationEntryPoint authenticationErrorHandler;
@Resource
private JwtAccessDeniedHandler jwtAccessDeniedHandler;
@Resource
private ApplicationContext applicationContext;
@Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
// 去除 ROLE_ 前缀
return new GrantedAuthorityDefaults("");
}
@Bean
public PasswordEncoder passwordEncoder() {
// 密码加密方式
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// 搜寻匿名标记 url: @AnonymousAccess
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = applicationContext.getBean(RequestMappingHandlerMapping.class).getHandlerMethods();
Set<String> anonymousUrls = new HashSet<>();
for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {
HandlerMethod handlerMethod = infoEntry.getValue();
AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
if (null != anonymousAccess) {
anonymousUrls.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
}
}
httpSecurity
// 禁用 CSRF
.csrf().disable()
.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
// 授权异常
.exceptionHandling()
.authenticationEntryPoint(authenticationErrorHandler)
.accessDeniedHandler(jwtAccessDeniedHandler)
// 防止iframe 造成跨域
.and()
.headers()
.frameOptions()
.disable()
// 不创建会话
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 静态资源等等
.antMatchers(
HttpMethod.GET,
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/webSocket/**"
).permitAll()
// swagger 文档
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/*/api-docs").permitAll()
.antMatchers("/auth/*").permitAll()
.antMatchers("/test/*").permitAll()
// 文件
.antMatchers("/avatar/**").permitAll()
.antMatchers("/file/**").permitAll()
// 阿里巴巴 druid
.antMatchers("/druid/**").permitAll()
// 放行OPTIONS请求
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// 自定义匿名访问所有url放行 : 允许匿名和带权限以及登录用户访问
.antMatchers(anonymousUrls.toArray(new String[0])).permitAll()
// 所有请求都需要认证
.anyRequest().authenticated()
.and().apply(securityConfigurerAdapter());
}
private TokenConfigurer securityConfigurerAdapter() {
return new TokenConfigurer(tokenProvider);
}
}
模拟登陆
主要是为了小程序、公众号和第三方授权使用
@GetMapping(value = "/testLogin")
@ApiOperation(value = "模拟登陆")
public GeneralResult<JSONObject> testLogin(HttpServletRequest request){
//这里可以根据用户id或者username等唯一值获取用户信息
UserDetails userDetails = userDetailsService.loadUserByUsername("test");
//生成授权对象,这里密码是不可逆的,所以随便乱输也可以囧
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(),
userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
// 生成令牌
String token = tokenProvider.createToken(authentication);
final JwtUser jwtUser = (JwtUser) authentication.getPrincipal();
// 保存在线信息 自有方法,放入到redis缓存中
onlineUserService.save(jwtUser, token, request);
// 返回 token 与 用户信息
JSONObject result = new JSONObject();
result.put("token", properties.getTokenStartWith() + token);
result.put("user", jwtUser);
//判断是否需要单点登陆,如果是单点登录,清除用户其他登录token数据
onlineUserService.checkLoginOnUser(userDetails.getUsername(), token);
return getResult(result);
}