传统情况下,在过滤器中做权限验证,Spring Secuirty也是在Filter中进行权限验证。

创建并注册过滤器

package com.awizdata.edubank.config;

import com.awizdata.edubank.security.JwtAuthenticationTokenFilter;
import com.awizdata.edubank.security.JwtLoginFilter;
import com.awizdata.edubank.security.JwtUserDetailsServiceImpl;
import com.awizdata.edubank.security.matches.SkipPathRequestMatcher; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; import java.util.Arrays;
import java.util.List; @Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { public static final String TOKEN_BASED_ENTRY_POINT = "/limit/**";
public static final String TOKEN_AUTH_ENTRY_POINT = "/auth/**";
public static final String TOKEN_OPEN_ENTRY_POINT = "/open/**"; @Bean
public SkipPathRequestMatcher skipPathRequestMatcher() {
List<String> pathsToSkip = Arrays.asList(TOKEN_AUTH_ENTRY_POINT);
return new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_ENTRY_POINT);
} @Bean
public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {
return new JwtAuthenticationTokenFilter(skipPathRequestMatcher());
} //创建登录过滤器
@Bean
public JwtLoginFilter jwtLoginFilter() {
return new JwtLoginFilter(authenticationManager());
} //重写userDetailsService,查询数据库进行用户名密码验证
@Override
@Bean
public UserDetailsService userDetailsService() {
return new JwtUserDetailsServiceImpl();
} @Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(this.passwordEncoder());
daoAuthenticationProvider.setUserDetailsService(this.userDetailsService());
return daoAuthenticationProvider;
} @Override
@Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(daoAuthenticationProvider()));
} // 装载BCrypt密码编码器
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
} @Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors().and()
// 由于使用的是JWT,我们这里不需要csrf
.csrf().disable()
// 基于token,所以不需要session, Spring Security永远不会创建HttpSession,它不会使用HttpSession来获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// 允许对于网站静态资源的无授权访问
.antMatchers(HttpMethod.GET,
"/",
"/error",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js"
).permitAll()
// 对于获取token的rest api要允许匿名访问
.antMatchers(TOKEN_AUTH_ENTRY_POINT, TOKEN_OPEN_ENTRY_POINT).permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
//使用自定义过滤器
.addFilter(this.jwtLoginFilter())
.addFilterAfter(jwtAuthenticationTokenFilter(), JwtLoginFilter.class);
// 禁用缓存
httpSecurity.headers().cacheControl();
}
}

2 Spring Security登录校验流程图

Spring Security登录验证的本质:用户登录后获得用户名和密码,根据用户名获取用户信息,并比对密码判断用户能否验证通过,并在此过程中对帐号是否已被锁定、账号是否可用、帐号是否已经过期、用户凭证是否已经过期。下面根据代码跟踪进一步分析验证过程

3源码分析

1 AbstractAuthenticationProcessingFilter.class

用户登录访问首先访问此过滤器,并调用doFilter方法

 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
//判断访问是否需要过滤
if(!this.requiresAuthentication(request, response)) {
chain.doFilter(request, response);
} else {
if(this.logger.isDebugEnabled()) {
this.logger.debug("Request is to process authentication");
} Authentication authResult;
//调用子类UsernamePasswordAuthenticationFilter实现
try {
authResult = this.attemptAuthentication(request, response);
if(authResult == null) {
return;
}
//session控制策略,因为用jwt,故不进行深入分析
this.sessionStrategy.onAuthentication(authResult, request, response);
} catch (InternalAuthenticationServiceException var8) {
this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
//验证失败,调用子类unsuccessfulAuthentication方法
this.unsuccessfulAuthentication(request, response, var8);
return;
} catch (AuthenticationException var9) {
//验证失败,调用子类unsuccessfulAuthentication方法
this.unsuccessfulAuthentication(request, response, var9);
return;
} if(this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
//验证成功,调用子类successfulAuthentication方法
this.successfulAuthentication(request, response, chain, authResult);
}
}

2 UsernamePasswordAuthenticationFilter

AbstractAuthenticationProcessingFilter 的 doFilter 方法中调用了 UsernamePasswordAuthenticationFilter 的 attemptAuthentication方法

 public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private String usernameParameter = "username";
private String passwordParameter = "password";
private boolean postOnly = true; //拦截POST形式的/login请求
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
} 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 {
//获取Parameter中的用户名和密码
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);
//调用子类getAuthenticationManager()方法获取AuthenticationManager并调用起authenticate方法进行验证
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);
} //记录remoteAddress、sessionId
protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
} public void setUsernameParameter(String usernameParameter) {
Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
this.usernameParameter = usernameParameter;
} public void setPasswordParameter(String passwordParameter) {
Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
this.passwordParameter = passwordParameter;
} public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
} public final String getUsernameParameter() {
return this.usernameParameter;
} public final String getPasswordParameter() {
return this.passwordParameter;
}
}

3 ProviderManager

UsernamePasswordAuthenticationFilter 中调用this.getAuthenticationManager().authenticate(authRequest);从WebSecurityConfig(见下图) 可以发现this.getAuthenticationManager().authenticate(authRequest)调用ProviderManager的authenticate方法,
并在authenticate方法中调用了daoAuthenticationProvider的authenticate方法

 //
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
// package org.springframework.security.authentication; import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AccountStatusException;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.ProviderNotFoundException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.util.Assert; public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
private static final Log logger = LogFactory.getLog(ProviderManager.class);
private AuthenticationEventPublisher eventPublisher;
private List<AuthenticationProvider> providers;
protected MessageSourceAccessor messages;
private AuthenticationManager parent;
private boolean eraseCredentialsAfterAuthentication; public ProviderManager(List<AuthenticationProvider> providers) {
this(providers, (AuthenticationManager)null);
} public ProviderManager(List<AuthenticationProvider> providers, AuthenticationManager parent) {
this.eventPublisher = new ProviderManager.NullEventPublisher();
this.providers = Collections.emptyList();
this.messages = SpringSecurityMessageSource.getAccessor();
this.eraseCredentialsAfterAuthentication = true;
Assert.notNull(providers, "providers list cannot be null");
this.providers = providers;
this.parent = parent;
this.checkState();
} public void afterPropertiesSet() throws Exception {
this.checkState();
} private void checkState() {
if(this.parent == null && this.providers.isEmpty()) {
throw new IllegalArgumentException("A parent AuthenticationManager or a list of AuthenticationProviders is required");
}
} public Authentication authenticate(Authentication authentication) throws AuthenticationException { Class toTest = authentication.getClass();//org.springframework.security.authentication.UsernamePasswordAuthenticationToken
Object lastException = null;
Authentication result = null;
boolean debug = logger.isDebugEnabled();
//获得authenticationManager列表这里只有daoAuthenticationProvider
Iterator e = this.getProviders().iterator(); while(e.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)e.next();
//authenticationProvider是否为UsernamePasswordAuthenticationToken或其子类
if(provider.supports(toTest)) {
if(debug) {
logger.debug("Authentication attempt using " + provider.getClass().getName());
} try {
result = provider.authenticate(authentication);
//copy Details到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;
}
}
} //未设置AuthenticationProvider的情况下,调用parent进行校验,这里parent未指定
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();
} //NullEventPublisher实现此方法,但未做任何处理
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;
}
} private void prepareException(AuthenticationException ex, Authentication auth) {
this.eventPublisher.publishAuthenticationFailure(ex, auth);
} private void copyDetails(Authentication source, Authentication dest) {
if(dest instanceof AbstractAuthenticationToken && dest.getDetails() == null) {
AbstractAuthenticationToken token = (AbstractAuthenticationToken)dest;
token.setDetails(source.getDetails());
} } public List<AuthenticationProvider> getProviders() {
return this.providers;
} public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
} public void setAuthenticationEventPublisher(AuthenticationEventPublisher eventPublisher) {
Assert.notNull(eventPublisher, "AuthenticationEventPublisher cannot be null");
this.eventPublisher = eventPublisher;
} public void setEraseCredentialsAfterAuthentication(boolean eraseSecretData) {
this.eraseCredentialsAfterAuthentication = eraseSecretData;
} public boolean isEraseCredentialsAfterAuthentication() {
return this.eraseCredentialsAfterAuthentication;
} private static final class NullEventPublisher implements AuthenticationEventPublisher {
private NullEventPublisher() {
} public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) {
} public void publishAuthenticationSuccess(Authentication authentication) {
}
}
}

4 AbstractUserDetailsAuthenticationProvider

daoAuthenticationProvider类并未实现authenticate方法,故这里调用了其父类(抽象类)AbstractUserDetailsAuthenticationProvider的authenticate方法
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
// package org.springframework.security.authentication.dao; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.userdetails.cache.NullUserCache;
import org.springframework.util.Assert; public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
protected final Log logger = LogFactory.getLog(this.getClass());
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private UserCache userCache = new NullUserCache();
private boolean forcePrincipalAsString = false;
protected boolean hideUserNotFoundExceptions = true;
private UserDetailsChecker preAuthenticationChecks = new AbstractUserDetailsAuthenticationProvider.DefaultPreAuthenticationChecks();
private UserDetailsChecker postAuthenticationChecks = new AbstractUserDetailsAuthenticationProvider.DefaultPostAuthenticationChecks();
private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); public AbstractUserDetailsAuthenticationProvider() {
} protected abstract void additionalAuthenticationChecks(UserDetails var1, UsernamePasswordAuthenticationToken var2) throws AuthenticationException; public final void afterPropertiesSet() throws Exception {
Assert.notNull(this.userCache, "A user cache must be set");
Assert.notNull(this.messages, "A message source must be set");
this.doAfterPropertiesSet();
} public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//类型判断
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported"));
//提取username
String username = authentication.getPrincipal() == null?"NONE_PROVIDED":authentication.getName();
//使用缓存
boolean cacheWasUsed = true;
//根据username从缓存中获取User信息
UserDetails user = this.userCache.getUserFromCache(username);
//缓存中不存在对应User信息,则重新从数据库加载
if(user == null) {
cacheWasUsed = false; try {
//从数据库加载User信息
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
} catch (UsernameNotFoundException var6) {
this.logger.debug("User \'" + username + "\' not found");
if(this.hideUserNotFoundExceptions) {
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
} throw var6;
} Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
} try {
//校验账号是否被锁定,是否可用,是否过期
this.preAuthenticationChecks.check(user);
//判断密码是否匹配(DaoAuthenticationProvider中实现)
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
} catch (AuthenticationException var7) {
if(!cacheWasUsed) {
throw var7;
} cacheWasUsed = false;
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
this.preAuthenticationChecks.check(user);
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
} // 判断用户凭证是否已经过期
this.postAuthenticationChecks.check(user);
if(!cacheWasUsed) {
//用户信息放入缓存
this.userCache.putUserInCache(user);
} Object principalToReturn = user;
if(this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
} //生成UsernamePasswordAuthenticationToken返回
return this.createSuccessAuthentication(principalToReturn, authentication, user);
} protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
return result;
} protected void doAfterPropertiesSet() throws Exception {
} public UserCache getUserCache() {
return this.userCache;
} public boolean isForcePrincipalAsString() {
return this.forcePrincipalAsString;
} public boolean isHideUserNotFoundExceptions() {
return this.hideUserNotFoundExceptions;
} protected abstract UserDetails retrieveUser(String var1, UsernamePasswordAuthenticationToken var2) throws AuthenticationException; public void setForcePrincipalAsString(boolean forcePrincipalAsString) {
this.forcePrincipalAsString = forcePrincipalAsString;
} public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) {
this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
} public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
} public void setUserCache(UserCache userCache) {
this.userCache = userCache;
} public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
} protected UserDetailsChecker getPreAuthenticationChecks() {
return this.preAuthenticationChecks;
} public void setPreAuthenticationChecks(UserDetailsChecker preAuthenticationChecks) {
this.preAuthenticationChecks = preAuthenticationChecks;
} protected UserDetailsChecker getPostAuthenticationChecks() {
return this.postAuthenticationChecks;
} public void setPostAuthenticationChecks(UserDetailsChecker postAuthenticationChecks) {
this.postAuthenticationChecks = postAuthenticationChecks;
} public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
this.authoritiesMapper = authoritiesMapper;
} private class DefaultPostAuthenticationChecks implements UserDetailsChecker {
private DefaultPostAuthenticationChecks() {
} public void check(UserDetails user) {
if(!user.isCredentialsNonExpired()) {
AbstractUserDetailsAuthenticationProvider.this.logger.debug("User account credentials have expired");
throw new CredentialsExpiredException(AbstractUserDetailsAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.credentialsExpired", "User credentials have expired"));
}
}
} private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
private DefaultPreAuthenticationChecks() {
} public void check(UserDetails user) {
if(!user.isAccountNonLocked()) {
AbstractUserDetailsAuthenticationProvider.this.logger.debug("User account is locked");
throw new LockedException(AbstractUserDetailsAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"));
} else if(!user.isEnabled()) {
AbstractUserDetailsAuthenticationProvider.this.logger.debug("User account is disabled");
throw new DisabledException(AbstractUserDetailsAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
} else if(!user.isAccountNonExpired()) {
AbstractUserDetailsAuthenticationProvider.this.logger.debug("User account is expired");
throw new AccountExpiredException(AbstractUserDetailsAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
}
}
}
}

5 DaoAuthenticationProvider

抽象类AbstractUserDetailsAuthenticationProvider中 核心方法additionalAuthenticationChecks  retrieveUser 均在DaoAuthenticationProvider 中实现

 public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
private PasswordEncoder passwordEncoder;
private volatile String userNotFoundEncodedPassword;
private UserDetailsService userDetailsService; public DaoAuthenticationProvider() {
this.setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
} //验证密码是否正确
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"));
}
}
} protected void doAfterPropertiesSet() throws Exception {
Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
} //根据username 重新请求User信息
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
this.prepareTimingAttackProtection(); try {
//这里自己做实现
UserDetails ex = this.getUserDetailsService().loadUserByUsername(username);
if(ex == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
} else {
return ex;
}
} catch (UsernameNotFoundException var4) {
this.mitigateAgainstTimingAttack(authentication);
throw var4;
} catch (InternalAuthenticationServiceException var5) {
throw var5;
} catch (Exception var6) {
throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
}
}

6 UserDetailsService 实现

 @Service
public class JwtUserDetailsServiceImpl implements UserDetailsService { @Autowired
private RcyUserDAO rcyUserDAO; @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
RcyUserPO user = rcyUserDAO.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username));
} else {
return JwtUserFactory.create(user);
}
}
}

7  JwtLoginFilter

验证成功调用JwtLoginFilter successfulAuthentication方法,并生成token返回给前台

验证失败调用unsuccessfulAuthentication,根据异常给前台做不同提示

 @Component
public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter { @Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private ObjectMapper mapper; public JwtLoginFilter(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
} @Value("${jwt.tokenHead}")
private String tokenHead; //登录验证成功
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
BackResult<Map<String, String>> result = new BackResult<>();
result.setCode(RESPONSE_CODE.BACK_CODE_SUCCESS.value); Map<String, String> data = new HashMap<>(); //获取User信息
UserDetails userDetails = (UserDetails) authResult.getPrincipal();
((JwtUser)userDetails).setLoginWay(this.getLoginWay(request)); //生成token
String token = jwtTokenUtil.generateToken(userDetails);
data.put("token", tokenHead + token);
data.put("userType", ((JwtUser)userDetails).getUserType());
data.put("userId",((JwtUser)userDetails).getId());
data.put("roles", StringUtils.collectionToDelimitedString(userDetails.getAuthorities(), ",")); result.setData(data); response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
mapper.writeValue(response.getWriter(), result);
} /**
* 获取登录途径
* @param request
* @return
*/
private String getLoginWay(HttpServletRequest request) {
String loginWayParam = "loginWay";
String loginWay = request.getParameter(loginWayParam);
if (JwtUser.LOGIN_WAY_APP.equals(loginWay)) {
return JwtUser.LOGIN_WAY_APP;
} else {
return JwtUser.LOGIN_WAY_PC;
}
} //登录验证失败
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
BackResult<String> result = new BackResult<>();
result.setCode(RESPONSE_CODE.BACK_CODE_FAIL.value);
//根据不同的异常做不同提示
String exceptions;
if (failed instanceof BadCredentialsException) {
exceptions = "username or password err";
} else {
exceptions = "login failed";
}
// 忽略用户不存在问题
result.setExceptions("login failed"); response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
mapper.writeValue(response.getWriter(), result);
}
}

至此,Spring Security登录校验涉及源码分析完毕

我们总结一下,使用Spring Security做登录校验需要做的工作

1 创建过滤器JwtLoginFilter 该类继承UsernamePasswordAuthenticationFilter 并重写successfulAuthentication unsuccessfulAuthentication方法

2 配置WebSecurityConfig 该类继承WebSecurityConfigurerAdapter,并在configure方法中注册过滤器JwtLoginFilter

3 注册Bean JwtLoginFilter、DaoAuthenticationProvider、AuthenticationManager 、UserDetailsService并对UserDetailsService做具体实现

Spring Security 登录校验 源码解析的更多相关文章

  1. 【Spring】Spring IOC原理及源码解析之scope=request、session

    一.容器 1. 容器 抛出一个议点:BeanFactory是IOC容器,而ApplicationContex则是Spring容器. 什么是容器?Collection和Container这两个单词都有存 ...

  2. Spring Boot系列(三):Spring Boot整合Mybatis源码解析

    一.Mybatis回顾 1.MyBatis介绍 Mybatis是一个半ORM框架,它使用简单的 XML 或注解用于配置和原始映射,将接口和Java的POJOs(普通的Java 对象)映射成数据库中的记 ...

  3. Spring Boot @Enable*注解源码解析及自定义@Enable*

      Spring Boot 一个重要的特点就是自动配置,约定大于配置,几乎所有组件使用其本身约定好的默认配置就可以使用,大大减轻配置的麻烦.其实现自动配置一个方式就是使用@Enable*注解,见其名知 ...

  4. Spring Security 架构与源码分析

    Spring Security 主要实现了Authentication(认证,解决who are you? ) 和 Access Control(访问控制,也就是what are you allowe ...

  5. Spring Boot自动配置源码解析(基于Spring Boot 2.0.2.RELEASE)

    在Spring Boot官方介绍中,首一段话是这样的(如下图).我们可以大概了解到其所表达的含义:我们可以利用Spring Boot写很少的配置来创建一个非常方便的基于Spring整合第三方类库的单体 ...

  6. Spring中AOP相关源码解析

    前言 在Spring中AOP是我们使用的非常频繁的一个特性.通过AOP我们可以补足一些面向对象编程中不足或难以实现的部分. AOP 前置理论 首先在学习源码之前我们需要了解关于AOP的相关概念如切点切 ...

  7. Spring Boot入门,源码解析

    目录 1.Spring Boot简介 2.微服务 3.Spring Boot HelloWorld 3.1 创建一个Maven工程 3.2 导入依赖Spring Boot相关的依赖 3.3 编写一个主 ...

  8. spring MVC 原理及源码解析

    首先要知道springmvc的核心控制器dispatcherServlet(继承自httpServlet) 一般httpServlet的请求过程: 1.初始化(创建servlet实例时)时会执行ser ...

  9. Spring Session使用及源码解析

    参照: http://blog.csdn.net/wojiaolinaaa/article/details/62424642 总结点spring session的一些知识点: spring通过过滤器, ...

随机推荐

  1. webpack中使用DefinePlugin定义全局变量

    DefinePlugin可以在编译时期创建全局变量.DefinePlugin是webpack注入全局变量的插件,通常使用该插件来判别代码运行的环境变量.

  2. arcgis api for js入门开发系列十九图层在线编辑

    本篇主要讲述的是利用arcgis api实现图层在线编辑功能模块,效果图如下: 实现思路: 1.arcgis server发布的FeatureServer服务提供的图层在线编辑能力: 2.实现的在线编 ...

  3. pyltp安装踩坑记录

    LTP(Language Technology Platform)由哈工大社会计算与信息检索研究中心开发,提供包括中文分词.词性标注.命名实体识别.依存句法分析.语义角色标注等丰富. 高效.精准的自然 ...

  4. MongoDB 基础(2019年开篇)

    MongoDB基础知识: 1.什么是MongoDB NoSQL(NoSQL=Not Only SQL),意即"不仅仅是SQL". MongoDB是一个介于关系数据库和非关系数据库之 ...

  5. Windows Management Instrumentation 服务卸载并重新创建

    SC delete Winmgmt sc create Winmgmt binPath= "C:\Windows\System32\svchost.exe -k netsvcs" ...

  6. SQLServer之创建Transact-SQL游标

    什么是游标 结果集,结果集就是select查询之后返回的所有行数据的集合. 游标则是处理结果集的一种机制吧,它可以定位到结果集中的某一行,多数据进行读写,也可以移动游标定位到你所需要的行中进行操作数据 ...

  7. MySQL SET数据类型

    SET: 多选字符串数据类型,适合存储“多个值”. 设定set的时候,同样需要设定“固定的几个值”:存储的时候,可以存储其中的若干个值. 设定set的格式: 字段名称  SET("选项1&q ...

  8. ztree搜索节点并展开

    web <div class="zTreeC"> <div class="searchL" lay-filter="searchL& ...

  9. 定义工作,解读自我——IT帮2019年2月线下活动回顾

    本次活动是在北京和深圳两个分站同步进行的,IT团建委员会负责策划和组织,北京站由帮主周老师.王兵老师主导,深圳站由副帮主兼深圳站长陈焕老师主导. 几位老师都是有着丰富的工作经历和人生体验的导师,他们不 ...

  10. Nginx作为HTTP服务器--Nginx配置图片服务器

      首先安装nginx安装环境 nginx是C语言开发,建议在linux上运行,本教程使用Centos6.5作为安装环境. --> gcc 安装nginx需要先将官网下载的源码进行编译,编译依赖 ...