springSecurity 登录以及用户账号密码解析原理

9次阅读

共计 12941 个字符,预计需要花费 33 分钟才能阅读完成。

springSecurity 拦截器链

用户登录基本流程处理如下:
1 SecurityContextPersistenceFilter
2 AbstractAuthenticationProcessingFilter
3 UsernamePasswordAuthenticationFilter
4 AuthenticationManager
5 AuthenticationProvider
6 userDetailsService
7 userDetails
8 认证通过
9 SecurityContext
10 SecurityContextHolder
11 AuthenticationSuccessHandler
用户页面登录
1 首先进入 SecurityContextPersistenceFilter 拦截器
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
。。。。。省略
//HttpRequestResponseHolder 对象
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
// 判断 session 是否存在,如果不存在则新建一个 session
SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
boolean var13 = false;

try {
var13 = true;
// 将 securiryContext 放入 SecurityContextHolder 中
SecurityContextHolder.setContext(contextBeforeChainExecution);
// 调用下一个拦截器,也就是之后所有的拦截器
chain.doFilter(holder.getRequest(), holder.getResponse());
var13 = false;
} finally {
if (var13) {
// 获取从 context
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
// 清空 SecurityContextHolder 中的 contex 临时保存
SecurityContextHolder.clearContext();
// 保存后面过滤器生成的数据 到 SecurityContextRepository 中
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
request.removeAttribute(“__spring_security_scpf_applied”);
if (debug) {
this.logger.debug(“SecurityContextHolder now cleared, as request processing completed”);
}

}
}
// 从 SecurityContextHolder 获取 SecurityContext 实例 8
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
// 清空 SecurityContextHolder 中的 SecurityContext
SecurityContextHolder.clearContext();
// 将 SecurityContext 实例保存到 session 中,以便下次请求时候用 9
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
request.removeAttribute(“__spring_security_scpf_applied”);
if (debug) {
this.logger.debug(“SecurityContextHolder now cleared, as request processing completed”);
}

}
}
loadContext 判断 session 是否存在 没有新建一个
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
HttpServletRequest request = requestResponseHolder.getRequest();
HttpServletResponse response = requestResponseHolder.getResponse();
// 如果 session 不存在则返回 null
HttpSession httpSession = request.getSession(false);
// 根据 private String springSecurityContextKey = “SPRING_SECURITY_CONTEXT”;
// 获取原来的 session
SecurityContext context = this.readSecurityContextFromSession(httpSession);
if (context == null) {
if (this.logger.isDebugEnabled()) {
this.logger.debug(“No SecurityContext was available from the HttpSession: ” + httpSession + “. A new one will be created.”);
}
// 如果 session 为 null 则新建一个
context = this.generateNewContext();
}
// 将 session 保存到 内部类 SaveToSessionResponseWrapper 中
HttpSessionSecurityContextRepository.SaveToSessionResponseWrapper wrappedResponse = new HttpSessionSecurityContextRepository.SaveToSessionResponseWrapper(response, request, httpSession != null, context);
// 保存在 HttpRequestResponseHolder 对象中
requestResponseHolder.setResponse(wrappedResponse);
if (this.isServlet3) {
requestResponseHolder.setRequest(new HttpSessionSecurityContextRepository.Servlet3SaveToSessionRequestWrapper(request, wrappedResponse));
}

return context;
}
readSecurityContextFromSession 方法中 根据判断 session 是否存在 会根据“SPRING_SECURITY_CONTEXT”
获取 session
用户登录即是认证
登录的实现方式:
2 进入 AbstractAuthenticationProcessingFilter 类

3 UsernamePasswordAuthenticationFilter 实现了类 AbstractAuthenticationProcessingFilter 调用自身的 attemptAuthentication 方法
获取用户名和密码,构建 token
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals(“POST”)) {
throw new AuthenticationServiceException(“Authentication method not supported: ” + request.getMethod());
} else {
String username = this.obtainUsername(request);
String password = this.obtainPassword(request);
if (username == null) {
username = “”;
}

if (password == null) {
password = “”;
}

username = username.trim();
// 构建 token,此时没有进行验证
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
// AuthenticationManager 将 token 传递给 的 authenticate 方法 进行 token 验证
return this.getAuthenticationManager().authenticate(authRequest);
}
}
// 获取密码
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(this.passwordParameter);
}
// 获取账号
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(this.usernameParameter);
}
调用 authenticate 方法,进行 token
4 AuthenticationManager 接口
1 AuthenticationManager 接口
public interface AuthenticationManager {
Authentication authenticate(Authentication var1) throws AuthenticationException;
}
2 ProviderManager 实现了 AuthenticationManager 接口
authenticate 方法中
result = provider.authenticate(authentication); // 返回成功的认证
重写 authenticate 方法 来获取用户验证信息
5 AuthenticationProvider 接口中方法
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
AbstractUserDetailsAuthenticationProvider 实现了 AuthenticationProvider 接口
public Authentication authenticate(Authentication authentication) //authentication 传递过来 token
throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
() -> messages.getMessage(
“AbstractUserDetailsAuthenticationProvider.onlySupports”,
“Only UsernamePasswordAuthenticationToken is supported”));

// Determine username 获取密码
String username = (authentication.getPrincipal() == null) ? “NONE_PROVIDED”
: authentication.getName();

boolean cacheWasUsed = true;
// 从缓存中获取
UserDetails user = this.userCache.getUserFromCache(username);
// 没有缓存
if (user == null) {
cacheWasUsed = false;

try {
// 查询数据库 获取用户账号密码
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException notFound) {
logger.debug(“User ‘” + username + “‘ not found”);

// 此处 不能抛出异常 UsernameNotFoundException
if (hideUserNotFoundExceptions) {
throw new BadCredentialsException(messages.getMessage(
“AbstractUserDetailsAuthenticationProvider.badCredentials”,
“Bad credentials”));
}
else {
throw notFound;
}
}

Assert.notNull(user,
“retrieveUser returned null – a violation of the interface contract”);
}

try {
// 检查账号是否过期等操作
preAuthenticationChecks.check(user);
//
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (AuthenticationException exception) {
if (cacheWasUsed) {
// There was a problem, so try again after checking
// we’re using latest data (i.e. not from the cache)
cacheWasUsed = false;
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
else {
throw exception;
}
}

postAuthenticationChecks.check(user);

if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}

Object principalToReturn = user;

if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
// 创建成功的认证 Authentication
return createSuccessAuthentication(principalToReturn, authentication, user);
}
retrieveUser 方法查询用户数据:
调用自身的抽象方法
protected abstract UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException;
DaoAuthenticationProvider 实现类
DaoAuthenticationProvider 继承 AbstractUserDetailsAuthenticationProvider 重写 retrieveUser 方法
用来根据用户名查询用户数据
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
// 根据用户名查询数据
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
“UserDetailsService returned null, which is an interface contract violation”);
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}
6 UserServiceDatils 接口 调用 loadUserByUsername 查询数据
7 userDetails 对象返回数据
UserServiceDatils 接口需要自己实现 (6.7 一起进行)
public class MUserDetailsService implements UserDetailsService {

Logger logger = LoggerFactory.getLogger(getClass());

@Autowired
private UsersMapper usersMapper;

/**
*@description: 需要从数据库中通过用户名来查询用户的信息和用户所属的角色
*@author: wangl
*@time: 2019/1/8 10:44
*@version 1.0
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

logger.info(“=========== 授权 ============”);

UserDetails userDeatils = null;

// 通过用户名 查询密码
Users users = usersMapper.selectUserByUserName(username);

Set<MGrantedAuthority> authorities = new HashSet<>();
// 查询用户角色
if(null != users){
logger.info(“ 用户 = ” + users.getUsername() + “—-” + users.getPassword());
// 查询用户权限
List<Users> usersList = usersMapper.selectRolesAndResourceByUserId(users.getId());
if(null != usersList && usersList.size()>0){
usersList.forEach(user->{
// 存放 role name 或者 权限名字
authorities.add(new MGrantedAuthority(user.getResourceName()));
});
}else{
System.out.println(“ 用户无权限。。”);

throw new BadCredentialsException(“not found … “);
}

7 返回数据
userDeatils = new Users(users.getUsername(),users.getPassword(),authorities);

}else{

throw new BadCredentialsException(“ 用户名不存在 ”);
}

return userDeatils;
}
查询用户数据之后
返回查询到的 loadUser 对象
然后用户账号是否被锁定,过期等验证
this.preAuthenticationChecks.check(user);

密码验证
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();
// 匹配密码
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”));
}
}
}
自定义密码验证器
@Slf4j
@Component
public class PasswordEncorder implements PasswordEncoder {
@Override
public String encode(CharSequence charSequence) {
log.info(“============ charSequence.toString() ============== ” + charSequence.toString());
return MD5Util.encode(charSequence.toString());
}

@Override
public boolean matches(CharSequence charSequence, String s) {

String pwd = charSequence.toString();
log.info(“ 前端传过来密码为:” + pwd);
log.info(“ 加密后密码为:” + MD5Util.encode(charSequence.toString()));
//s 应在数据库中加密
if(MD5Util.encode(charSequence.toString()).equals(MD5Util.encode(s))){
return true;
}

throw new DisabledException(“– 密码错误 –“);
}

}
11 AuthenticationSuccessHandler
/**
*@description: 自定义登陆成功处理类
*@author: wangl
*@time: 2019/1/14 17:46
*@version 1.0
*/

@Component
public class MyAuthenctiationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
logger.info(“ 登陆成功。。”);
String name = authentication.getName();
HttpSession session = request.getSession();
session.setAttribute(“user”,name);
response.sendRedirect(“/success”);
//super.onAuthenticationSuccess(request, response, authentication);
}
}
配置类
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
MUserDetailsService mUserDetailsService; //userDetails

@Autowired
MyFilterSecurityInterceptor myFilterSecurityInterceptor; // 自定义拦截器

@Autowired
PasswordEncoder passwordEncoder; // 密码验证器

@Autowired
private MyAuthenctiationFailureHandler myAuthenctiationFailureHandler;// 失败处理
@Autowired
private MyAuthenctiationSuccessHandler myAuthenctiationSuccessHandler;// 成功处理

@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().mvcMatchers(“/static/**”); // 过滤静态资源
}

/** 定义认证用户信息获取来源,密码校验规则等 **/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 从内存中获取
// 明文方式提交
/* auth.inMemoryAuthentication().withUser(“zs”).password(“1234”).roles(“USER”)
.and().withUser(“admin”).password(“1234”).roles(“ADMIN”); // 从内存中获取
*/

auth.userDetailsService(mUserDetailsService) .passwordEncoder(passwordEncoder); // 密码加密方式
/*auth.authenticationProvider(customAuthenticationProvider)
.authenticationProvider(authenticationProvider()) // 增加
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());*/

/*
auth.inMemoryAuthentication().passwordEncoder(new PasswordEncorder()).withUser(“zs”).password(“1234”).roles(“USER”)
.and().withUser(“admin”).password(“1234”).roles(“ADMIN”)
;*/
}

/** 定义安全策略 **/
@Override
protected void configure(HttpSecurity http) throws Exception {

http.authorizeRequests() //// 配置安全策略
.antMatchers(“/”,”/login.html”,”/loginPage”).permitAll() // 定义请求不需要验证
//.antMatchers(“/admin/next”).hasRole(“ADMIN”) // 设置只有管理员才能访问的 url
//.antMatchers(“/admin/**”).hasAnyRole(“ADMIN”) // 设置多个角色访问的 url
.anyRequest().authenticated() // 其余所有请求都需要验证

.and()

.formLogin() // 配置登录页面

.loginPage(“/loginPage”) // 登录页面访问路径
.loginProcessingUrl(“/login”) // 登录页面提交表单路径
.successHandler(myAuthenctiationSuccessHandler)// 成功页面
.failureHandler(myAuthenctiationFailureHandler) // 失败后跳转路径

.and()
.logout() // 登出不需要验证
.logoutUrl(“/logout”).permitAll()

// .and()

//.authorizeRequests()
//.antMatchers(“/admin/next”).hasRole(“ADMIN”)
//.and()
//.rememberMe() // 记住我功能
//.tokenValiditySeconds(10000);

// 自定义的拦截器,在适当的地方加入
http.addFilterAt(myFilterSecurityInterceptor, FilterSecurityInterceptor.class);

http.csrf().disable(); /// 关闭默认的 csrf 认证
}

}

正文完
 0