spring-security认证过程的分析及自定义登录
2017-10-10 11:46
701 查看
首先spring-security配置认证过滤器,它是spring-security处理业务的入口。用户如果不重写过滤器,使用默认的过滤器UsernamePasswordAuthenticationFilter。它继承了抽象类AbstractAuthenticationProcessingFilter,该类注入了authenticationManager属性,配置security的认证管理器。
过滤器UsernamePasswordAuthenticationFilter拿到用户名密码创建一个UsernamePasswordAuthenticationToken,通过认证管理器进行认证代码如下
认证管理器配置如下
认证管理器是通过接口AuthenticationManager处理的
ProviderManager类实现了这个接口,它的认证代码如下
看以上代码,认证过程继续调用AuthenticationProvider,它是一个接口,调用抽象类AbstractUserDetailsAuthenticationProvider进行认证。
DaoAuthenticationProvider类继承了抽象类AbstractUserDetailsAuthenticationProvider,重写了获取用户的方法,通过配置中的user-service-ref,调用获取UserDetails的方法。
接下来对用户进行验证如果没有配置password-encoder,默认调用PlaintextPasswordEncoder进行密码的对比。否则调用用户配置的密码加密类进行密码比对。
基于上述分析,现我们有如下业务需求,通过qq等第三方登录,绑定系统的账号,通过绑定账号直接进行security的登录,那么我们该怎么做呢?主要代码如下
通过绑定用户查询数据库,获取用户密码,手动创建一个UsernamePasswordAuthenticationToken,调用配置的认证管理器,返回一个认证令牌,注册到security容器中。即可。
这里有一个问题,如果user-service-ref配置了密码加密方式,那么数据库中存入的用户密码为加密后的密码,调用认证管理器会把数据库查询的密码再使用加密方式加密一次,这样密码比对就会失败。
解决这个问题,重写认证管理器。
这样,就不会使用配置的认证管理器,进行加密验证了。
<beans:bean id="myLoginFilter" class="com.yinhai.modules.security.spring.app.filter.Ta3AuthenticationFilter"> <!--认证管理器--> <beans:property name="authenticationManager" ref="myAuthenticationManager" /> <!-- 验证成功后执行扩展的处理 --> <beans:property name="authenticationSuccessHandler" ref="taOnAuthenticationSuccessHandler" /> <!-- 验证失败后执行扩展的处理 --> <beans:property name="authenticationFailureHandler" ref="taAuthenticationFailureHandler" /> <beans:property name="filterProcessesUrl" value="/j_spring_security_check" /> <beans:property name="userBpo" ref="userBpo" /> <beans:property name="sessionAuthenticationStrategy" ref="sas"></beans:property> </beans:bean>
过滤器UsernamePasswordAuthenticationFilter拿到用户名密码创建一个UsernamePasswordAuthenticationToken,通过认证管理器进行认证代码如下
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(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); this.setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } }
认证管理器配置如下
<authentication-manager alias="myAuthenticationManager"> <authentication-provider user-service-ref="taUserDetailsService"> <password-encoder ref="md5Encoder"> <salt-source ref="saltSource"/> </password-encoder> </authentication-provider> </authentication-manager>
认证管理器是通过接口AuthenticationManager处理的
public interface AuthenticationManager { Authentication authenticate(Authentication var1) throws AuthenticationException; }
ProviderManager类实现了这个接口,它的认证代码如下
public Authentication authenticate(Authentication authentication) throws AuthenticationException { Class<? extends Authentication> toTest = authentication.getClass(); AuthenticationException lastException = null; Authentication result = null; boolean debug = logger.isDebugEnabled(); Iterator var6 = this.getProviders().iterator(); while(var6.hasNext()) { AuthenticationProvider provider = (AuthenticationProvider)var6.next(); if (provider.supports(toTest)) { if (debug) { logger.debug("Authentication attempt using " + provider.getClass().getName()); } try { result = provider.authenticate(authentication); if (result != null) { this.copyDetails(authentication, result); break; } } catch (AccountStatusException var11) { this.prepareException(var11, authentication); throw var11; } catch (InternalAuthenticationServiceException var12) { this.prepareException(var12, authentication); throw var12; } catch (AuthenticationException var13) { lastException = var13; } } } if (result == null && this.parent != null) { try { result = this.parent.authenticate(authentication); } catch (ProviderNotFoundException var9) { ; } catch (AuthenticationException var10) { lastException = var10; } } if (result != null) { if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) { ((CredentialsContainer)result).eraseCredentials(); } this.eventPublisher.publishAuthenticationSuccess(result); return result; } else { if (lastException == null) { lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}")); } this.prepareException((AuthenticationException)lastException, authentication); throw lastException; } }
AuthenticationProvider provider = (AuthenticationProvider)var6.next(); result = provider.authenticate(authentication);
看以上代码,认证过程继续调用AuthenticationProvider,它是一个接口,调用抽象类AbstractUserDetailsAuthenticationProvider进行认证。
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
DaoAuthenticationProvider类继承了抽象类AbstractUserDetailsAuthenticationProvider,重写了获取用户的方法,通过配置中的user-service-ref,调用获取UserDetails的方法。
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
接下来对用户进行验证如果没有配置password-encoder,默认调用PlaintextPasswordEncoder进行密码的对比。否则调用用户配置的密码加密类进行密码比对。
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { Object salt = null; if (this.saltSource != null) { salt = this.saltSource.getSalt(userDetails); } 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.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) { this.logger.debug("Authentication failed: password does not match stored value"); throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } } }
基于上述分析,现我们有如下业务需求,通过qq等第三方登录,绑定系统的账号,通过绑定账号直接进行security的登录,那么我们该怎么做呢?主要代码如下
String userName = request.getParameter("userName"); String pwd = request.getParameter("pwd"); userName = userName.trim(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userName, pwd); Authentication authentication = authenticationManager.authenticate(authRequest); //调用loadUserByUsername SecurityContextHolder.getContext().setAuthentication(authentication); HttpSession session = request.getSession(); session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());// 这个非常重要,否则验证后将无法登陆
通过绑定用户查询数据库,获取用户密码,手动创建一个UsernamePasswordAuthenticationToken,调用配置的认证管理器,返回一个认证令牌,注册到security容器中。即可。
这里有一个问题,如果user-service-ref配置了密码加密方式,那么数据库中存入的用户密码为加密后的密码,调用认证管理器会把数据库查询的密码再使用加密方式加密一次,这样密码比对就会失败。
解决这个问题,重写认证管理器。
public Authentication authenticate(Authentication auth) throws AuthenticationException { String username = auth.getName(); UserDetails userDetails = taUserDetailsService.loadUserByUsername(username); Object principal = userDetails; UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, auth.getCredentials(), this.authoritiesMapper.mapAuthorities(userDetails.getAuthorities())); result.setDetails(auth.getDetails()); return result; }
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(loginId, password); Authentication authentication = simpleAuthenticationManager.authenticate(authRequest); SecurityContextHolder.getContext().setAuthentication(authentication);
这样,就不会使用配置的认证管理器,进行加密验证了。
相关文章推荐
- 【Spring实战】----Security4.1.3认证过程源码分析
- spring实战-Spring-security实现用户权限认证登录
- Spring-Security自定义登录页&inMemoryAuthentication验证
- Spring Boot + Spring Security 防止用户在多处同时登录(一个用户同时只能登录一次)及源码分析
- Linphone-android 登录过程增加自定义消息头流程分析
- 结合WAS简析J2EE规范中的登录认证和非规范中的注销以及通过filter增加自定义处理过程
- spring集成shiro实现登录认证自定义验证功能(认证采用国密SM4算法)
- spring boot + mybatis + spring security(自定义登录界面)环境搭建
- Spring-Security笔记2 自定义登录界面
- (七)、SpringBoot + SpringSecurity 简单登录认证
- spring-security过程分析
- spring boot + mybatis + spring security(自定义登录界面)环境搭建
- spring security自定义认证登录的全过程记录
- [安全] 使用 SpringBoot + SpringSecurity 做登录认证
- 【Spring实战】----security4.1.3认证的过程以及原请求信息的缓存及恢复(RequestCache)
- spring-security4.2实现登录退出以及权限配置
- Spring-Security权限管理框架(1)——根据角色权限登录
- Spring Security源码分析六:Spring Social社交登录源码解析
- springboot(十四):springboot整合shiro-登录认证和权限管理(转)
- 自定义windows登录认证(微软 Credential Providers 详解一《调用原理》)