SpringSecurity登录原理(源码级讲解)
一、简单叙述
首先会进入UsernamePasswordAuthenticationFilter并且设置权限为null和是否授权为false,然后进入ProviderManager查找支持UsernamepasswordAuthenticationToken的provider并且调用provider.authenticate(authentication);再然后就是UserDetailsService接口的实现类(也就是自己真正具体的业务了),这时候都检查过了后,就会回调UsernamePasswordAuthenticationFilter并且设置权限(具体业务所查出的权限)和设置授权为true(因为这时候确实所有关卡都检查过了)。
PS:云里雾绕的?没关系,接下里看我们每一步骤都具体的深入到源码级别的去分析。
二、源码分析
UsernamePasswordAuthenticationFilter
// 继承了AbstractAuthenticationProcessingFilter
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
// 认证请求的方式必须为POST
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
// 获取用户名
String username = obtainUsername(request);
// 获取密码
String password = obtainPassword(request);
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (username == <span class="hljs-keyword"><span class="hljs-keyword">null</span></span>) {
username = <span class="hljs-string"><span class="hljs-string">""</span></span>;
}
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (password == <span class="hljs-keyword"><span class="hljs-keyword">null</span></span>) {
password = <span class="hljs-string"><span class="hljs-string">""</span></span>;
}
<span class="hljs-comment"><span class="hljs-comment">// 用户名去空白</span></span>
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = <span class="hljs-keyword"><span class="hljs-keyword">new</span></span> UsernamePasswordAuthenticationToken(
username, password);
<span class="hljs-comment"><span class="hljs-comment">// Allow subclasses to set the "details" property</span></span>
setDetails(request, authRequest);
<span class="hljs-keyword"><span class="hljs-keyword">return</span></span> <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.getAuthenticationManager().authenticate(authRequest);
}
}
可以发现继承了AbstractAuthenticationProcessingFilter,那我们就来看下此类
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
implements ApplicationEventPublisherAware, MessageSourceAware {
<span class="hljs-comment"><span class="hljs-comment">// 过滤器doFilter方法</span></span>
<span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">public</span></span></span><span class="hljs-function"> </span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">void</span></span></span><span class="hljs-function"> </span><span class="hljs-title"><span class="hljs-function"><span class="hljs-title">doFilter</span></span></span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">(ServletRequest req, ServletResponse res, FilterChain chain)</span></span></span><span class="hljs-function">
</span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">throws</span></span></span><span class="hljs-function"> IOException, ServletException </span></span>{
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
<span class="hljs-comment"><span class="hljs-comment">/*
* 判断当前filter是否可以处理当前请求,若不行,则交给下一个filter去处理。
*/</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
<span class="hljs-keyword"><span class="hljs-keyword">return</span></span>;
}
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (logger.isDebugEnabled()) {
logger.debug(<span class="hljs-string"><span class="hljs-string">"Request is to process authentication"</span></span>);
}
Authentication authResult;
<span class="hljs-keyword"><span class="hljs-keyword">try</span></span> {
<span class="hljs-comment"><span class="hljs-comment">// 很关键!!!调用了子类(UsernamePasswordAuthenticationFilter)的方法</span></span>
authResult = attemptAuthentication(request, response);
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (authResult == <span class="hljs-keyword"><span class="hljs-keyword">null</span></span>) {
<span class="hljs-comment"><span class="hljs-comment">// return immediately as subclass has indicated that it hasn't completed</span></span>
<span class="hljs-comment"><span class="hljs-comment">// authentication</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">return</span></span>;
}
<span class="hljs-comment"><span class="hljs-comment">// 最终认证成功后,会处理一些与session相关的方法(比如将认证信息存到session等操作)。</span></span>
sessionStrategy.onAuthentication(authResult, request, response);
}
<span class="hljs-keyword"><span class="hljs-keyword">catch</span></span> (InternalAuthenticationServiceException failed) {
logger.error(
<span class="hljs-string"><span class="hljs-string">"An internal error occurred while trying to authenticate the user."</span></span>,
failed);
<span class="hljs-comment"><span class="hljs-comment">// 认证失败后的一些处理。</span></span>
unsuccessfulAuthentication(request, response, failed);
<span class="hljs-keyword"><span class="hljs-keyword">return</span></span>;
}
<span class="hljs-keyword"><span class="hljs-keyword">catch</span></span> (AuthenticationException failed) {
<span class="hljs-comment"><span class="hljs-comment">// Authentication failed</span></span>
unsuccessfulAuthentication(request, response, failed);
<span class="hljs-keyword"><span class="hljs-keyword">return</span></span>;
}
<span class="hljs-comment"><span class="hljs-comment">// Authentication success</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
<span class="hljs-comment"><span class="hljs-comment">/*
* 最终认证成功后的相关回调方法,主要将当前的认证信息放到SecurityContextHolder中
* 并调用成功处理器做相应的操作。
*/</span></span>
successfulAuthentication(request, response, chain, authResult);
}
}
PS:看到这里估计很多人在骂娘了,什么玩意,直接复制粘贴也不讲解,不要急,上面只是看下类结构,下面来具体分析!这里只分析主要代码,不是很主要也不是很相关的不作讲解,有兴趣的自己去读。
(一)、 父类的处理流程
1、继承了父类,父类是个过滤器,所以肯定先执行AbstractAuthenticationProcessingFilter.doFilter(),此方法首先判断当前的filter是否可以处理当前请求,不可以的话则交给下一个filter处理。
/*
* 判断当前filter是否可以处理当前请求,若不行,则交给下一个filter去处理。
*/
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
2、调用此抽象类的子类UsernamePasswordAuthenticationFilter.attemptAuthentication(request, response)方法做具体的操作。
// 很关键!!!调用了子类(UsernamePasswordAuthenticationFilter)的方法
authResult = attemptAuthentication(request, response);
3、最终认证成功后做一些成功后的session操作,比如将认证信息存到session等。
// 最终认证成功后,会处理一些与session相关的方法(比如将认证信息存到session等操作)。
sessionStrategy.onAuthentication(authResult, request, response);
4、最终认证成功后的相关回调方法,主要将当前的认证信息放到SecurityContextHolder中并调用成功处理器做相应的操作。
successfulAuthentication(request, response, chain, authResult);
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (logger.isDebugEnabled()) {
logger.debug(<span class="hljs-string"><span class="hljs-string">"Authentication success. Updating SecurityContextHolder to contain: "</span></span> + authResult);
}
<span class="hljs-comment"><span class="hljs-comment">// 将当前的认证信息放到SecurityContextHolder中</span></span>
SecurityContextHolder.getContext().setAuthentication(authResult);
rememberMeServices.loginSuccess(request, response, authResult);
<span class="hljs-comment"><span class="hljs-comment">// Fire event</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.eventPublisher != <span class="hljs-keyword"><span class="hljs-keyword">null</span></span>) {
eventPublisher.publishEvent(<span class="hljs-keyword"><span class="hljs-keyword">new</span></span> InteractiveAuthenticationSuccessEvent(
authResult, <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.getClass()));
}
<span class="hljs-comment"><span class="hljs-comment">// 调用成功处理器,可以自己实现AuthenticationSuccessHandler接口重写方法写自己的逻辑</span></span>
successHandler.onAuthenticationSuccess(request, response, authResult);
}
(二)、子类的处理流程
1、父类的authResult = attemptAuthentication(request, response);触发了自类的方法。
2、此方法首先判断请求方式是不是POST提交,必须是POST
// 认证请求的方式必须为POST
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
3、从请求中获取username和password,并做一些处理
// 获取用户名
String username = obtainUsername(request);
// 获取密码
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
// 用户名去空白
username = username.trim();
4、封装Authenticaiton类的实现类UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super((Collection)null);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(false);
}
PS:为什么这个构造器设置权限为null?
super((Collection)null);,并且设置是否授权为false?this.setAuthenticated(false);道理很简单,因为我们这是刚刚登陆过来,你的账号密码对不对我们都没验证呢,所以这里是未授权,权限null。
5、调用AuthenticationManager的authenticate方法进行验证
return this.getAuthenticationManager().authenticate(authRequest);
(三)、AuthenticationManager处理流程
1、怎么触发的?
return this.getAuthenticationManager().authenticate(authRequest);
PS:交由
AuthenticationManager接口的ProviderManager实现类处理。
2、ProviderManager.authenticate(Authentication authentication);
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class toTest = authentication.getClass();
Object lastException = null;
Authentication result = null;
boolean debug = logger.isDebugEnabled();
// 拿到全部的provider
Iterator e = this.getProviders().iterator();
// 遍历provider
while(e.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)e.next();
// 挨着个的校验是否支持当前token
if(provider.supports(toTest)) {
if(debug) {
logger.debug("Authentication attempt using " + provider.getClass().getName());
}
<span class="hljs-keyword"><span class="hljs-keyword">try</span></span> {
<span class="hljs-comment"><span class="hljs-comment">// 找到后直接break,并由当前provider来进行校验工作</span></span>
result = provider.authenticate(authentication);
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span>(result != <span class="hljs-keyword"><span class="hljs-keyword">null</span></span>) {
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.copyDetails(authentication, result);
<span class="hljs-keyword"><span class="hljs-keyword">break</span></span>;
}
} <span class="hljs-keyword"><span class="hljs-keyword">catch</span></span> (AccountStatusException var11) {
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.prepareException(var11, authentication);
<span class="hljs-keyword"><span class="hljs-keyword">throw</span></span> var11;
} <span class="hljs-keyword"><span class="hljs-keyword">catch</span></span> (InternalAuthenticationServiceException var12) {
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.prepareException(var12, authentication);
<span class="hljs-keyword"><span class="hljs-keyword">throw</span></span> var12;
} <span class="hljs-keyword"><span class="hljs-keyword">catch</span></span> (AuthenticationException var13) {
lastException = var13;
}
}
}
<span class="hljs-comment"><span class="hljs-comment">// 若没有一个支持,则尝试交给父类来执行</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span>(result == <span class="hljs-keyword"><span class="hljs-keyword">null</span></span> && <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.parent != <span class="hljs-keyword"><span class="hljs-keyword">null</span></span>) {
<span class="hljs-keyword"><span class="hljs-keyword">try</span></span> {
result = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.parent.authenticate(authentication);
} <span class="hljs-keyword"><span class="hljs-keyword">catch</span></span> (ProviderNotFoundException var9) {
;
} <span class="hljs-keyword"><span class="hljs-keyword">catch</span></span> (AuthenticationException var10) {
lastException = var10;
}
}
..........................
}
**3、此方法遍历所有的Providers,然后依次执行验证方法看是否支持UsernamepasswordAuthenticationToken**
// 拿到全部的provider
Iterator e = this.getProviders().iterator();
// 遍历provider
while(e.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)e.next();
// 挨着个的校验是否支持当前token
if(provider.supports(toTest)) {
if(debug) {
logger.debug("Authentication attempt using " + provider.getClass().getName());
}
}
}
4、若有一个能够支持当前token,则直接交由此provider处理并break。
// 找到后直接break,并由当前provider来进行校验工作
result = provider.authenticate(authentication);
if(result != null) {
this.copyDetails(authentication, result);
break;
}
5、若没一个provider验证成功,则交由父类来尝试处理
// 若没有一个支持,则尝试交给父类来执行
if(result == null && this.parent != null) {
try {
result = this.parent.authenticate(authentication);
} catch (ProviderNotFoundException var9) {
;
} catch (AuthenticationException var10) {
lastException = var10;
}
}
(四)、AuthenticationProvider处理流程
1、怎么触发的?
// 由上一步的ProviderManager的authenticate方法来触发
result = provider.authenticate(authentication);
PS:这里交由
AuthenticationProvider接口的实现类DaoAuthenticationProvider来处理。
2、DaoAuthenticationProvider
// 继承了AbstractUserDetailsAuthenticationProvider
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
<span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">protected</span></span></span><span class="hljs-function"> </span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">final</span></span></span><span class="hljs-function"> UserDetails </span><span class="hljs-title"><span class="hljs-function"><span class="hljs-title">retrieveUser</span></span></span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">(String username, UsernamePasswordAuthenticationToken authentication)</span></span></span><span class="hljs-function"> </span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">throws</span></span></span><span class="hljs-function"> AuthenticationException </span></span>{
UserDetails loadedUser;
<span class="hljs-keyword"><span class="hljs-keyword">try</span></span> {
<span class="hljs-comment"><span class="hljs-comment">/*
* 调用UserDetailsService接口的loadUserByUsername方法,
* 此方法就是我们自己定义的类去实现接口重写的方法,处理我们自己的业务逻辑。
*/</span></span>
loadedUser = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.getUserDetailsService().loadUserByUsername(username);
} <span class="hljs-keyword"><span class="hljs-keyword">catch</span></span> (UsernameNotFoundException var6) {
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span>(authentication.getCredentials() != <span class="hljs-keyword"><span class="hljs-keyword">null</span></span>) {
String presentedPassword = authentication.getCredentials().toString();
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.passwordEncoder.isPasswordValid(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.userNotFoundEncodedPassword, presentedPassword, (Object)<span class="hljs-keyword"><span class="hljs-keyword">null</span></span>);
}
<span class="hljs-keyword"><span class="hljs-keyword">throw</span></span> var6;
} <span class="hljs-keyword"><span class="hljs-keyword">catch</span></span> (Exception var7) {
<span class="hljs-keyword"><span class="hljs-keyword">throw</span></span> <span class="hljs-keyword"><span class="hljs-keyword">new</span></span> InternalAuthenticationServiceException(var7.getMessage(), var7);
}
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span>(loadedUser == <span class="hljs-keyword"><span class="hljs-keyword">null</span></span>) {
<span class="hljs-keyword"><span class="hljs-keyword">throw</span></span> <span class="hljs-keyword"><span class="hljs-keyword">new</span></span> InternalAuthenticationServiceException(<span class="hljs-string"><span class="hljs-string">"UserDetailsService returned null, which is an interface contract violation"</span></span>);
} <span class="hljs-keyword"><span class="hljs-keyword">else</span></span> {
<span class="hljs-keyword"><span class="hljs-keyword">return</span></span> loadedUser;
}
}
}
3、继承了AbstractUserDetailsAuthenticationProvider
// 实现了AuthenticationProvider接口
public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
<span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">public</span></span></span><span class="hljs-function"> Authentication </span><span class="hljs-title"><span class="hljs-function"><span class="hljs-title">authenticate</span></span></span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">(Authentication authentication)</span></span></span><span class="hljs-function"> </span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">throws</span></span></span><span class="hljs-function"> AuthenticationException </span></span>{
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.messages.getMessage(<span class="hljs-string"><span class="hljs-string">"AbstractUserDetailsAuthenticationProvider.onlySupports"</span></span>, <span class="hljs-string"><span class="hljs-string">"Only UsernamePasswordAuthenticationToken is supported"</span></span>));
String username = authentication.getPrincipal() == <span class="hljs-keyword"><span class="hljs-keyword">null</span></span>?<span class="hljs-string"><span class="hljs-string">"NONE_PROVIDED"</span></span>:authentication.getName();
<span class="hljs-keyword"><span class="hljs-keyword">boolean</span></span> cacheWasUsed = <span class="hljs-keyword"><span class="hljs-keyword">true</span></span>;
UserDetails user = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.userCache.getUserFromCache(username);
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span>(user == <span class="hljs-keyword"><span class="hljs-keyword">null</span></span>) {
cacheWasUsed = <span class="hljs-keyword"><span class="hljs-keyword">false</span></span>;
<span class="hljs-keyword"><span class="hljs-keyword">try</span></span> {
<span class="hljs-comment"><span class="hljs-comment">// 调用自类retrieveUser</span></span>
user = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
} <span class="hljs-keyword"><span class="hljs-keyword">catch</span></span> (UsernameNotFoundException var6) {
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.logger.debug(<span class="hljs-string"><span class="hljs-string">"User \'"</span></span> + username + <span class="hljs-string"><span class="hljs-string">"\' not found"</span></span>);
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span>(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.hideUserNotFoundExceptions) {
<span class="hljs-keyword"><span class="hljs-keyword">throw</span></span> <span class="hljs-keyword"><span class="hljs-keyword">new</span></span> BadCredentialsException(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.messages.getMessage(<span class="hljs-string"><span class="hljs-string">"AbstractUserDetailsAuthenticationProvider.badCredentials"</span></span>, <span class="hljs-string"><span class="hljs-string">"Bad credentials"</span></span>));
}
<span class="hljs-keyword"><span class="hljs-keyword">throw</span></span> var6;
}
Assert.notNull(user, <span class="hljs-string"><span class="hljs-string">"retrieveUser returned null - a violation of the interface contract"</span></span>);
}
<span class="hljs-keyword"><span class="hljs-keyword">try</span></span> {
<span class="hljs-comment"><span class="hljs-comment">/*
* 前检查由DefaultPreAuthenticationChecks类实现(主要判断当前用户是否锁定,过期,冻结
* User接口)
*/</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.preAuthenticationChecks.check(user);
<span class="hljs-comment"><span class="hljs-comment">// 子类具体实现</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
} <span class="hljs-keyword"><span class="hljs-keyword">catch</span></span> (AuthenticationException var7) {
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span>(!cacheWasUsed) {
<span class="hljs-keyword"><span class="hljs-keyword">throw</span></span> var7;
}
cacheWasUsed = <span class="hljs-keyword"><span class="hljs-keyword">false</span></span>;
user = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.preAuthenticationChecks.check(user);
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
}
<span class="hljs-comment"><span class="hljs-comment">// 检测用户密码是否过期</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.postAuthenticationChecks.check(user);
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span>(!cacheWasUsed) {
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.userCache.putUserInCache(user);
}
Object principalToReturn = user;
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span>(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
<span class="hljs-keyword"><span class="hljs-keyword">return</span></span> <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.createSuccessAuthentication(principalToReturn, authentication, user);
}
}
4、AbstractUserDetailsAuthenticationProvider.authenticate()首先调用了user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
PS:调用的是
DaoAuthenticationProvider.retrieveUser()
5、调用我们自己的业务处理类
/*
* 调用UserDetailsService接口的loadUserByUsername方法,
* 此方法就是我们自己定义的类去实现接口重写的方法,处理我们自己的业务逻辑。
*/
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
比如:
/**
* @author chentongwei@bshf360.com 2018-03-26 13:15
*/
@Service
public class MyUserDetailsService implements UserDetailsService {
<span class="hljs-keyword"><span class="hljs-keyword">private</span></span> Logger logger = LoggerFactory.getLogger(getClass());
<span class="hljs-meta"><span class="hljs-meta">@Autowired</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">private</span></span> PasswordEncoder passwordEncoder;
<span class="hljs-meta"><span class="hljs-meta">@Override</span></span>
<span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">public</span></span></span><span class="hljs-function"> UserDetails </span><span class="hljs-title"><span class="hljs-function"><span class="hljs-title">loadUserByUsername</span></span></span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">(String username)</span></span></span><span class="hljs-function"> </span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">throws</span></span></span><span class="hljs-function"> UsernameNotFoundException </span></span>{
logger.info(<span class="hljs-string"><span class="hljs-string">"表单登录用户名:"</span></span> + username);
<span class="hljs-keyword"><span class="hljs-keyword">return</span></span> buildUser(username);
}
<span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">private</span></span></span><span class="hljs-function"> UserDetails </span><span class="hljs-title"><span class="hljs-function"><span class="hljs-title">buildUser</span></span></span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">(String username)</span></span></span><span class="hljs-function"> </span></span>{
<span class="hljs-comment"><span class="hljs-comment">/**
* passwordEncoder.encode这步骤应该放到注册接口去做,而这里只需要传一个从db查出来的pwd即可。
*
* passwordEncoder.encode("123456")每次打印出来都是不同的,虽然是同一个(123456)密码,
* 但是他会随机生成一个盐(salt),他会把随机生成的盐混到加密的密码里。Springsecurity验证(matches方法)的时候会将利用此盐解析出pwd,进行匹配。
* 这样的好处是:如果数据库里面有10个123456密码。但是被破解了1个,那么另外九个是安全的,因为db里存的串是不一样的。
*/</span></span>
String password = passwordEncoder.encode(<span class="hljs-string"><span class="hljs-string">"123456"</span></span>);
logger.info(<span class="hljs-string"><span class="hljs-string">"数据库密码是:"</span></span> + password);
<span class="hljs-comment"><span class="hljs-comment">// 这个User不一定必须用SpringSecurity的,可以写一个自定义实现UserDetails接口的类,然后把是否锁定等判断逻辑写进去。</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">return</span></span> <span class="hljs-keyword"><span class="hljs-keyword">new</span></span> User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList(<span class="hljs-string"><span class="hljs-string">"admin"</span></span>));
}
}
PS:注意:实现
UserDetailsService接口。可返回我们自己定义的User类,但User类要实现UserDetails接口
6、调用完retrieveUser方法继续回到抽象类的authenticate方法
7、首先做一些检查
/*
* 前检查由DefaultPreAuthenticationChecks类实现(主要判断当前用户是否锁定,过期,冻结
* User接口)
*/
this.preAuthenticationChecks.check(user);
// 检测用户密码是否过期
this.postAuthenticationChecks.check(user);
8、调用createSuccessAuthentication方法进行授权成功
return this.createSuccessAuthentication(principalToReturn, authentication, user);
// 成功授权
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
// 回调UsernamePasswordAuthenticationToken的构造器,这里调用的是授权成功的构造器
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
// 将认证信息的一块内容放到details
result.setDetails(authentication.getDetails());
return result;
}
public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
// 不在是null,而是传来的权限,这个权限就是我们自己定义的detailsService类所返回的,可以从db查
super(authorities);
this.principal = principal;
this.credentials = credentials;
// 这里是true,不在是false。
super.setAuthenticated(true);
}
9、回到起点
AbstractAuthenticationProcessingFilter.doFilter()
进行session存储和成功后的处理器的调用等
三、总结
只是简单说下类之间的调用顺序。
UsernamePasswordAuthenticationFilter
Authentication
AuthenticationManager
AuthenticationProvider
UserDetailsService
// 回到起点进行后续操作,比如缓存认证信息到session和调用成功后的处理器等等
UsernamePasswordAuthenticationFilter
四、Demo
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h2>标准登录页面</h2>
<h3>表单登录</h3>
<form action="login" method="post">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td colspan="2"><button type="submit">登录</button></td>
</tr>
</table>
</form>
</body>
</html>
http.formLogin()
// 默认表单登录页
.loginPage(SecurityConstant.DEFAULT_UNAUTHENTICATION_URL)
// 登录接口
.loginProcessingUrl(SecurityConstant.DEFAULT_LOGIN_PROCESSING_URL_FORM)
/**
* 常量
*
* @author chentongwei@bshf360.com 2018-03-26 11:40
*/
public interface SecurityConstant {
<span class="hljs-comment"><span class="hljs-comment">/**
* 默认登录页
*/</span></span>
String DEFAULT_LOGIN_PAGE_URL = <span class="hljs-string"><span class="hljs-string">"/default-login.html"</span></span>;
<span class="hljs-comment"><span class="hljs-comment">/**
* 默认的登录接口
*/</span></span>
String DEFAULT_LOGIN_PROCESSING_URL_FORM = <span class="hljs-string"><span class="hljs-string">"/login"</span></span>;
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
/**
* @author chentongwei@bshf360.com 2018-03-26 13:15
*/
@Service
public class MyUserDetailsService implements UserDetailsService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.info("表单登录用户名:" + username);
return buildUser(username);
}
private UserDetails buildUser(String username) {
/**
* passwordEncoder.encode这步骤应该放到注册接口去做,而这里只需要传一个从db查出来的pwd即可。
*
* passwordEncoder.encode("123456")每次打印出来都是不同的,虽然是同一个(123456)密码,
* 但是他会随机生成一个盐(salt),他会把随机生成的盐混到加密的密码里。Springsecurity验证(matches方法)的时候会将利用此盐解析出pwd,进行匹配。
* 这样的好处是:如果数据库里面有10个123456密码。但是被破解了1个,那么另外九个是安全的,因为db里存的串是不一样的。
*/
String password = passwordEncoder.encode("123456");
logger.info("数据库密码是:" + password);
// 这个User不一定必须用SpringSecurity的,可以写一个自定义实现UserDetails接口的类,然后把是否锁定等判断逻辑写进去。
return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
大功告成!
只需要一个html,一段配置,一个Service自己的业务类即可。
疑问:
1、接口login在哪定义的?
2、用户名username和密码password在哪接收的?
3、没有控制器怎么进入我们的MyUserDetailsService的方法?
解答:
1、SpringSecurity内置的,并且只能为POST
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
2、名称不能变,必须是username和password
public class UsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
// ~ Static fields/initializers
// =====================================================================================
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
}
3、自己看我上面的源码分析
SpringSecurity登录原理(源码级讲解)的更多相关文章
- 【Java编程实战】Metasploit_Java后门运行原理分析以及实现源码级免杀与JRE精简化
QQ:3496925334 文章作者:MG1937 CNBLOG博客ID:ALDYS4 未经许可,禁止转载 某日午睡,迷迷糊糊梦到Metasploit里有个Java平台的远控载荷,梦醒后,打开虚拟机, ...
- MapReduce的ReduceTask任务的运行源码级分析
MapReduce的MapTask任务的运行源码级分析 这篇文章好不容易恢复了...谢天谢地...这篇文章讲了MapTask的执行流程.咱们这一节讲解ReduceTask的执行流程.ReduceTas ...
- MapReduce的MapTask任务的运行源码级分析
TaskTracker任务初始化及启动task源码级分析 这篇文章中分析了任务的启动,每个task都会使用一个进程占用一个JVM来执行,org.apache.hadoop.mapred.Child方法 ...
- TaskTracker任务初始化及启动task源码级分析
在监听器初始化Job.JobTracker相应TaskTracker心跳.调度器分配task源码级分析中我们分析的Tasktracker发送心跳的机制,这一节我们分析TaskTracker接受JobT ...
- 源码级调试的XNU内核
i春秋翻译小组-FWorldCodeZ 源码级调试的XNU内核 无论你是在开发内核扩展,进行漏洞研究,还是还有其他需要进入macOS / iOS内核,XNU,有时你需要附加调试器.当你这样做时,使用源 ...
- Delphi制作QQ自动登录器源码
Delphi制作QQ自动登录器源码 http://www.cnblogs.com/sunsoft/archive/2011/02/25/1964967.html 以TM2009为例,检查了一下,未登 ...
- 深入理解Faiss 原理&源码 (一) 编译
目录 深入理解Faiss 原理&源码 (一) 编译 mac下安装 安装mac xcode工具包 安装 openblas 安装swig 安装libomp 编译faiss 附录 深入理解Faiss ...
- spring security之 默认登录页源码跟踪
spring security之 默认登录页源码跟踪 2021年的最后2个月,立个flag,要把Spring Security和Spring Security OAuth2的应用及主流程源码研究透 ...
- 创作gtk源码级vim帮助文档 tags
创作gtk源码级vim帮助文档 tags 缘由 那只有看到源码了.在linux源码上有个网站 http://lxr.linux.no /+trees, 可以很方面的查出相应版本的代码实现,gtk没有. ...
随机推荐
- docker无法删除镜像,Error: No such container,附docker常用命令
最近打算删除掉docker镜像但是发现有几个镜像就是删除不了,加了-f强制删除也不行,一直报Error: No such container的错误,最后终于找到了办法直接删除文件,步骤如下: 切换到r ...
- Directx11教程(42) 纹理映射(12)-简单的bump mapping
原文:Directx11教程(42) 纹理映射(12)-简单的bump mapping 有时候,我们只有一个粗糙的模型,但是我们想渲染纹理细节,比如一个砖墙,我们如何在只有一个平面的时候 ...
- asp.net ajax客户端框架如何调用Web Service
asp.net ajax客户端框架如何调用Web Service 1:Web Service类添加 [System.Web.Script.Services.ScriptService]特性2:需要异步 ...
- LeetCode153 Find Minimum in Rotated Sorted Array. LeetCode162 Find Peak Element
二分法相关 153. Find Minimum in Rotated Sorted Array Suppose a sorted array is rotated at some pivot unkn ...
- 【IOS】异常捕获 拒绝闪退 让应用从容的崩溃 UncaughtExceptionHandler
尽管大家都不愿意看到程序崩溃,但可能崩溃是每一个应用必须面对的现实.既然崩溃已经发生.无法阻挡了.那我们就让它崩也崩得淡定点吧. IOS SDK中提供了一个现成的函数 NSSetUncaughtExc ...
- CSS+div总结 标签: css 2016-01-17 11:35 926人阅读 评论(31) 收藏
根据学习计划,将视频进行了学习,之前就知道css是基础,然后一致认为既然是基础,应该比较简陋吧,结果经过学习才发现,css的效果也是很炫的啊,然后学习完了视频,自己又找了一些教程.下面就简单介绍一下我 ...
- 卸载ROS命令
ROS有问题需要卸载只需输入以下命令: sudo apt-get purge ros-* sudo rm -rf /etc/ros
- @atcoder - AGC034D@ Manhattan Max Matching
目录 @description@ @solution@ @accepted code@ @details@ @description@ 考虑一个二维平面,执行共 2*N 次操作: 前 N 次,第 i ...
- 1、Ubuntu 16.04 安装.net core
Register the Microsoft key register the product repository Install required dependencies 参考网址:https: ...
- jqLite
一.关于DOM导航的jqLite方法 children() 返回一组子元素.这个方法的jqLite实现不支持jQuery所提供的选择器特性 eq(index) 从一个元素集合中返回指定索引下的元素 f ...