前言
1. 初识SpringSecurity
权限管理
一般指根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自己被授权的资源。
两个核心概念:
认证:通过用户名和密码成功登陆系统后,让系统得到当前用户的角色身份。
授权:系统根据当前用户的角色,给其授予对应可以操作的权限资源。
三个对象
用户:主要包含用户名,密码和当前用户的角色信息,可实现认证操作。
角色:主要包含角色名称,角色描述和当前角色拥有的权限信息,可实现授权操作。
权限:权限也可以称为菜单,主要包含当前权限名称,url地址等信息,可实现动态展示菜单。
用户与角色是多对多的关系,角色与权限是多对多的关系,用户与权限没有直接关系
二者是通过角色来建立关联关系的。
初识SpringSecurity
SpringSecurity是一款非常优秀的权限管理框架。
- 采用AOP思想,基于servlet过滤器实现的安全框架。
- 提供了完善的认证机制和方法级的授权功能。
…
核心jar包
- pring-security-core.jar:核心包,任何Spring Security功能都需要此包。
- spring-security-web.jar:web工程必备,包含过滤器和相关的Web安全基础结构代码。
- spring-security-config.jar:用于解析xml配置文件,用到Spring Security的xml配置文件的就要用到此包。
- spring-security-taglibs.jar:Spring Security提供的动态标签库,jsp页面可以用。
pom依赖
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>5.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.2.1.RELEASE</version>
</dependency>
配置web.xml
<display-name>Archetype Created Web Application</display-name>
<!--Spring Security过滤器链,过滤器名称必须叫springSecurityFilterChain-->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<!-- 过滤器拦截所有请求 -->
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
配置SpringSecurity.xml
<?xml version="1.0" encoding="UTF-8"?>
...
xmlns:security="http://www.springframework.org/schema/security"
...
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<!--配置静态资源目录不经过过滤器,示例只写一个-->
<security:http pattern="/css/**" security="none"/>
...
<!--设置可以用spring的el表达式配置Spring Security并自动生成对应配置组件(过滤器)-->
<security:http auto-config="true" use-expressions="true">
<!--指定login.jsp页面可以匿名访问-->
<security:intercept-url pattern="/login.jsp" access="permitAll()"/>
<!--使用spring的el表达式来指定项目所有资源访问都必须有ROLE_USER或ROLE_ADMIN角色
/* 是拦截所有的文件夹,不包含子文件夹
/** 是拦截所有的文件夹及里面的子文件夹
-->
<security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER','ROLE_ADMIN')"/>
<!--指定自定义的认证页面-->
<security:form-login login-page="/login.jsp"
login-processing-url="/login"
default-target-url="index.jsp"
authentication-farlure-url="failer.jsp"
/>
<security:from-login logout-url="/logout"
logout-seccese-url="/login.jsp"
/>
<!--禁用csrf防护机制,不推荐-->
<!-- <security:csrf disable="true"/> -->
</security:http>
<!--设置Spring Security认证用户信息的来源 {noop}表示不加密认证
这部分配置可以在数据库查询,为了简便直接在这里写死
-->
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="user" password="{noop}user"
authorities="ROLE_USER" />
<security:user name="admin" password="{noop}admin"
authorities="ROLE_ADMIN" />
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
</beans>
登录界面
<%@taglib prefix="security" "uri='http://www.springframework.org/ security /tags"%>
<form action="${pageContext.request.contextPath}/login" method="post">
<!--在认证from表单内携带token,需要在文件头添加SpringSecurity标签库-->
<!--<security:csrfInput/>-->
<div class="form-group has-feedback">
<input type="text" name="username" class="form-control" laceholder="用户名">
<span class="glyphicon glyphicon-envelope form-control-feedback"></span>
</div>
<div class="form-group has-feedback">
<input type="password" name="password" class="form-control" placeholder="密码">
<span class="glyphicon glyphicon-lock form-control-feedback"></span>
</div>
<div class="col-xs-4">
<button type="submit" class="btn btn-primary btn-block btn-flat">登录</button>
</div>
<!-- /.col -->
</div>
</form>
常用过滤器
SecurityContextPersistenceFilter
SecurityContextPersistenceFilter主要是使用SecurityContextRepository在session中保存或更新一SecurityContext,并将SecurityContext给以后的过滤器使用,来为后续filter建立所需的上下文。SecurityContext中存储了当前用户的认证以及权限信息。
WebAsyncManagerIntegrationFilter
此过滤器用于集成SecurityContext到Spring异步执行机制中的WebAsyncManager
HeaderWriterFilter
向请求的Header中添加相应的信息,可在http标签内部使用security:headers来控制(仅限于JSP页面)
CsrfFilter
csrf又称跨域请求伪造,SpringSecurity会对所有post请求验证是否包含系统生成的csrf的token信息, 如果不包含,则报错。起到防止csrf攻击的效果。
LogoutFilter
匹配URL为/logout的请求,实现用户退出,清除认证信息。
UsernamePasswordAuthenticationFilter
认证操作全靠这个过滤器,默认匹配URL为/login且必须为POST请求。
DefaultLoginPageGeneratingFilter
如果没有在配置文件中指定认证页面,则由该过滤器生成一个默认认证页面。
DefaultLogoutPageGeneratingFilter
由此过滤器可以生产一个默认的退出登录页面
BasicAuthenticationFilter
此过滤器会自动解析HTTP请求中头部名字为Authentication,且以Basic开头的头信息。
RequestCacheAwareFilter
通过HttpSessionRequestCache内部维护了一个RequestCache,用于缓存HttpServletRequest
SecurityContextHolderAwareRequestFilter
针对ServletRequest进行了一次包装,使得request具有更加丰富的API
AnonymousAuthenticationFilter
当SecurityContextHolder中认证信息为空,则会创建一个匿名用户存入到SecurityContextHolder中。 spring security为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份。
当用户以游客身份登录的时候,也就是可以通过设置某些接口可以匿名访问
SessionManagementFilter
SecurityContextRepository限制同一用户开启多个会话的数量
ExceptionTranslationFilter
异常转换过滤器位于整个springSecurityFilterChain的后方,用来转换整个链路中出现的异常
FilterSecurityInterceptor
获取所配置资源访问的授权信息,根据SecurityContextHolder中存储的用户信息来决定其是否有权 限
该过滤器限制哪些资源可以访问,哪些不能够访问。
过滤器链加载原理
DelegatingFilterProxy
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
上面是web.xml中配置的名为springSecurityFilterChain
的过滤器DelegatingFilterProxy
,下面对DelegatingFilterProxy源码里重要代码进行说明
public class DelegatingFilterProxy extends GenericFilterBean {
@Nullable
private String contextAttribute;
@Nullable
private WebApplicationContext webApplicationContext;
@Nullable
private String targetBeanName;
private boolean targetFilterLifecycle;
@Nullable
private volatile Filter delegate;// 真正被加载的过滤器
private final Object delegateMonitor;
...
// 这个方法是过滤器的入口
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized(this.delegateMonitor) {
delegateToUse = this.delegate;
if (delegateToUse == null) {
WebApplicationContext wac = this.findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener or DispatcherServlet registered?");
}
// 1. 初始化过滤器delegate
delegateToUse = this.initDelegate(wac);
}
this.delegate = delegateToUse;
}
}
// 3. 执行FilterChainProxy,即delegateToUse过滤链接
this.invokeDelegate(delegateToUse, request, response, filterChain);
}
// 2. DelegatingFilterProxy通过springSecurityFilterChain这个名称,得到了一个FilterChainProxy过滤器,最终在第三步执行了这个过滤器。
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
// 断点得知targetBeanName 值 "springSecurityFilterChain"
String targetBeanName = this.getTargetBeanName();
Assert.state(targetBeanName != null, "No target bean name set");
// 断点得知delegate就是FilterChainProxy过滤器,
Filter delegate = (Filter)wac.getBean(targetBeanName, Filter.class);
if (this.isTargetFilterLifecycle()) {
delegate.init(this.getFilterConfig());
}
return delegate;
}
protected void invokeDelegate(Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
delegate.doFilter(request, response, filterChain);
}
}
FilterChainProxy
上面源码的第3步执行了FilterChainProxy
过滤器,
public class FilterChainProxy extends GenericFilterBean {
private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
private static final String FILTER_APPLIED = FilterChainProxy.class.getName().concat(".APPLIED");
private List<SecurityFilterChain> filterChains;
private FilterChainProxy.FilterChainValidator filterChainValidator;
private HttpFirewall firewall;
public FilterChainProxy() {
this.filterChainValidator = new FilterChainProxy.NullFilterChainValidator();
this.firewall = new StrictHttpFirewall();
}
// 先用SecurityFilterChain 对象初始化了FilterChainProxy
// 那么SecurityFilterChain 是什么,看后面代码
public FilterChainProxy(SecurityFilterChain chain) {
this(Arrays.asList(chain));
}
// SecurityFilterChain数组也能初始化了FilterChainProxy
// SecurityFilterChain到底是什么呢?
public FilterChainProxy(List<SecurityFilterChain> filterChains) {
this.filterChainValidator = new FilterChainProxy.NullFilterChainValidator();
this.firewall = new StrictHttpFirewall();
this.filterChains = filterChains;
}
// 继续看过滤器入口doFilter
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if (clearContext) {
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
this.doFilterInternal(request, response, chain);
} finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
} else {
// 1. 具体操作调用doFilterInternal
this.doFilterInternal(request, response, chain);
}
}
private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FirewalledRequest fwRequest = this.firewall.getFirewalledRequest((HttpServletRequest)request);
HttpServletResponse fwResponse = this.firewall.getFirewalledResponse((HttpServletResponse)response);
// 2. 封装过滤器链,多个在这里被封装进数组List集合,点进去getFilters方法看第3步
// 断点到这一步会发现十五个过滤器都在filters数组里
List<Filter> filters = this.getFilters((HttpServletRequest)fwRequest);
if (filters != null && filters.size() != 0) {
FilterChainProxy.VirtualFilterChain vfc = new FilterChainProxy.VirtualFilterChain(fwRequest, chain, filters);
// 4. 加载过滤器链
vfc.doFilter(fwRequest, fwResponse);
} else {
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(fwRequest) + (filters == null ? " has no matching filters" : " has an empty filter list"));
}
fwRequest.reset();
chain.doFilter(fwRequest, fwResponse);
}
}
private List<Filter> getFilters(HttpServletRequest request) {
Iterator var2 = this.filterChains.iterator();
// 3. 将过滤器链封装到SecurityFilterChain中,然后返回SecurityFilterChain.getFilters()
SecurityFilterChain chain;
do {
if (!var2.hasNext()) {
return null;
}
chain = (SecurityFilterChain)var2.next();
} while(!chain.matches(request));
return chain.getFilters();
}
SecurityFilterChain
上面源码看出过滤器都被封装进SecurityFilterChain中了
接下来看SecurityFilterChain,这是个接口,实现类也只有一个,这才是web.xml中配置的过滤器链对象!
// SecurityFilterChain接口
public interface SecurityFilterChain {
boolean matches(HttpServletRequest var1);
List<Filter> getFilters();
}
// 实现类
public final class DefaultSecurityFilterChain implements SecurityFilterChain {
private static final Log logger = LogFactory.getLog(DefaultSecurityFilterChain.class);
private final RequestMatcher requestMatcher;
private final List<Filter> filters;
public DefaultSecurityFilterChain(RequestMatcher requestMatcher, Filter... filters) {
this(requestMatcher, Arrays.asList(filters));
}
public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) {
logger.info("Creating filter chain: " + requestMatcher + ", " + filters);
this.requestMatcher = requestMatcher;
this.filters = new ArrayList(filters);
}
public RequestMatcher getRequestMatcher() {
return this.requestMatcher;
}
public List<Filter> getFilters() {
return this.filters;
}
public boolean matches(HttpServletRequest request) {
return this.requestMatcher.matches(request);
}
public String toString() {
return "[ " + this.requestMatcher + ", " + this.filters + "]";
}
}
CsrfFilter
SpringSecurity的csrf防护机制,CSRF(Cross-site request forgery)
跨站请求伪造,是一种难以防范的网络攻击方式。
SpringSecurity中CsrfFilter
过滤器说明
public final class CsrfFilter extends OncePerRequestFilter {
public static final RequestMatcher DEFAULT_CSRF_MATCHER = new CsrfFilter.DefaultRequiresCsrfMatcher();
private static final String SHOULD_NOT_FILTER = "SHOULD_NOT_FILTER" + CsrfFilter.class.getName();
private final Log logger = LogFactory.getLog(this.getClass());
private final CsrfTokenRepository tokenRepository;
private RequestMatcher requireCsrfProtectionMatcher;
private AccessDeniedHandler accessDeniedHandler;
public CsrfFilter(CsrfTokenRepository csrfTokenRepository) {
this.requireCsrfProtectionMatcher = DEFAULT_CSRF_MATCHER;
this.accessDeniedHandler = new AccessDeniedHandlerImpl();
Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null");
this.tokenRepository = csrfTokenRepository;
}
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
return Boolean.TRUE.equals(request.getAttribute(SHOULD_NOT_FILTER));
}
// 过滤器入口,从这个方法看出scrf机制吧请求方式分成两类来处理
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
request.setAttribute(HttpServletResponse.class.getName(), response);
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
boolean missingToken = csrfToken == null;
if (missingToken) {
csrfToken = this.tokenRepository.generateToken(request);
this.tokenRepository.saveToken(csrfToken, request, response);
}
request.setAttribute(CsrfToken.class.getName(), csrfToken);
request.setAttribute(csrfToken.getParameterName(), csrfToken);
// 第一类:“GET”,“HEAD”,“TRACE”,“OPTIONS”四类请求可以直接通过
if (!this.requireCsrfProtectionMatcher.matches(request)) {
filterChain.doFilter(request, response);
} else {
// 第二类:其他请求都要求携带token才能通过
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}
if (!csrfToken.getToken().equals(actualToken)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request));
}
if (missingToken) {
this.accessDeniedHandler.handle(request, response, new MissingCsrfTokenException(actualToken));
} else {
this.accessDeniedHandler.handle(request, response, new InvalidCsrfTokenException(csrfToken, actualToken));
}
} else {
filterChain.doFilter(request, response);
}
}
}
}
认证页面,请求方式为POST,所以会出现403
解决方案:方式一:直接禁用csrf,不推荐。方式二:在认证页面携带token请求。
禁用csrf
见上文中web.xml
中被注释的配置
<!--禁用csrf防护机制,不推荐-->
<security:csrf disable="true"/>
在认证页面携带token请求
见登录界面login.jsp
中,先在文件头添加SpringSecurity标签库,然后在from表单第一行添加<security:csrfInput/>
HttpSessionCsrfTokenRepository对象负责生成token并放入session域中
SpringSecurity使用数据库数据完成认证
认证流程
UsernamePasswordAuthenticationFilter
是主要负责认证的过滤器
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;
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
// 必须是POST请求
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中
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
// 获取并调用AuthenticationManager对象的authenticate方法实现认证
return this.getAuthenticationManager().authenticate(authRequest);
}
}
}
发送的登录请求,请求地址需要是 /login
,请求方法 post
, 获取并调用AuthenticationManager对象的方法实现认证
AuthenticationManager
然后看AuthenticationManager的实现类
// 接口
public interface AuthenticationManager {
Authentication authenticate(Authentication var1) throws AuthenticationException;
}
实现类ProviderManager
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;
// AuthenticationProvider: SpringSecurity针对每一种认证(微信、qq、账号密码等),都封装了一个AuthenticationProvider对象
public ProviderManager(List<AuthenticationProvider> providers) {
this(providers, (AuthenticationManager)null);
}
public ProviderManager(List<AuthenticationProvider> providers, AuthenticationManager parent) {
...
}
// 执行authenticate方法实现认证的
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
boolean debug = logger.isDebugEnabled();
Iterator var8 = this.getProviders().iterator();
// 循环所有AuthenticationProvider,匹配当前认证类型
while(var8.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var8.next();
if (provider.supports(toTest)) {
if (debug) {
logger.debug("Authentication attempt using " + provider.getClass().getName());
}
try {
// 找到对应认证类型,继续调用AuthenticationProvider对象的方法完成认证业务
result = provider.authenticate(authentication);
if (result != null) {
this.copyDetails(authentication, result);
break;
}
} catch (InternalAuthenticationServiceException | AccountStatusException var13) {
this.prepareException(var13, authentication);
throw var13;
} catch (AuthenticationException var14) {
lastException = var14;
}
}
...
}
}
AuthenticationProvider
抽象类AbstractUserDetailsAuthenticationProvider,其实现类DaoAuthenticationProvider
AbstractUserDetailsAuthenticationProvider.authenticate中调用AbstractUserDetailsAuthenticationProvider.retrieveUser
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
private PasswordEncoder passwordEncoder;
private volatile String userNotFoundEncodedPassword;
private UserDetailsService userDetailsService;
private UserDetailsPasswordService userDetailsPasswordService;
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
this.prepareTimingAttackProtection();
try {
// UserDetails: SpringSecurity自己的用户对象
// this.getUserDetailsService(): 得到UserDetailsService的一个实现类
// loadUserByUsername(username):里面就是真正的认证逻辑
// 我们需要编写一个UserDetailService的实现类,在loadUserByUsername方法中返回一个UserDetails对象
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
// 若返回null,异常,认证失败
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
} else {
// 得到userDetails对象,正常返回
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 Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
boolean upgradeEncoding = this.userDetailsPasswordService != null && this.passwordEncoder.upgradeEncoding(user.getPassword());
if (upgradeEncoding) {
String presentedPassword = authentication.getCredentials().toString();
String newPassword = this.passwordEncoder.encode(presentedPassword);
user = this.userDetailsPasswordService.updatePassword(user, newPassword);
}
return super.createSuccessAuthentication(principal, authentication, user);
}
private void prepareTimingAttackProtection() {
if (this.userNotFoundEncodedPassword == null) {
this.userNotFoundEncodedPassword = this.passwordEncoder.encode("userNotFoundPassword");
}
}
private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
}
}
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
this.passwordEncoder = passwordEncoder;
this.userNotFoundEncodedPassword = null;
}
protected PasswordEncoder getPasswordEncoder() {
return this.passwordEncoder;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
protected UserDetailsService getUserDetailsService() {
return this.userDetailsService;
}
public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
this.userDetailsPasswordService = userDetailsPasswordService;
}
}
这个UserDetails对象返回到AbstractUserDetailsAuthenticationProvider的authenticate
AbstractUserDetailsAuthenticationProvider
public abstract class AbstractUserDetailsAuthenticationProvider implements
...
// 最后一行返回,调用了createSuccessAuthentication方法,看下面
return createSuccessAuthentication(principalToReturn, authentication, user);
}
protected Authentication createSuccessAuthentication(Object principal,
Authentication authentication, UserDetails user) {
// 又封装了一次UsernamePasswordAuthenticationToken,刚开始封装过一次,再点进去看看这个构造方法
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
principal, authentication.getCredentials(),
authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
return result;
}
}
UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken有两个构造方法
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
// 认证前,两个参数
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}
// 认证成功后,三个参数
public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
// 看看父类AbstractAuthenticationToken做了什么
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true); // must use super, as we override
}
}
AbstractAuthenticationToken
super(null)对应上述两个参数情况,super(authorities)对应上述三个参数情况
public abstract class AbstractAuthenticationToken implements Authentication,
public AbstractAuthenticationToken(Collection<? extends GrantedAuthority> authorities) {
// 上述两个参数时
if (authorities == null) {
this.authorities = AuthorityUtils.NO_AUTHORITIES;
return;
}
// 上述三个参数时,多了添加权限信息的步骤
for (GrantedAuthority a : authorities) {
if (a == null) {
// 没有权限信息抛异常,咱们需要牢记自定义认证业务逻辑返回的UserDetails对象中一定要放置权限信息
throw new IllegalArgumentException(
"Authorities collection cannot contain any null elements");
}
}
ArrayList<GrantedAuthority> temp = new ArrayList<>(
authorities.size());
temp.addAll(authorities);
this.authorities = Collections.unmodifiableList(temp);
}
}
咱们回到最初的地方UsernamePasswordAuthenticationFilter,你看好看了,这可是个过滤器,咱们分析这么久,都没提到doFilter方法,你不觉得心里不踏实?
可是这里面也没有doFilter呀?那就从父类AbstractAuthenticationProcessingFilter中找,晕QAQ没完了。。。
AbstractAuthenticationProcessingFilter
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware {
// 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;
try {
authResult = this.attemptAuthentication(request, response);
if (authResult == null) {
return;
}
this.sessionStrategy.onAuthentication(authResult, request, response);
} catch (InternalAuthenticationServiceException var8) {
this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
// 失败
this.unsuccessfulAuthentication(request, response, var8);
return;
} catch (AuthenticationException var9) {
// 失败
this.unsuccessfulAuthentication(request, response, var9);
return;
}
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
// 认证成功
this.successfulAuthentication(request, response, chain, authResult);
}
}
// 认证成功走successfulAuthentication
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
}
// SecurityContextHolder.getContext()得到SecurityContext
// 将认证信息存储到SecurityContext中
SecurityContextHolder.getContext().setAuthentication(authResult);
// 登陆成功,调用rememberMeServices.loginSuccess
this.rememberMeServices.loginSuccess(request, response, authResult);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
// 认证失败走unsuccessfulAuthentication
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
SecurityContextHolder.clearContext();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Authentication request failed: " + failed.toString(), failed);
this.logger.debug("Updated SecurityContextHolder to contain null Authentication");
this.logger.debug("Delegating to authentication failure handler " + this.failureHandler);
}
// 登陆失败,调用rememberMeServices.loginFail
this.rememberMeServices.loginFail(request, response);
this.failureHandler.onAuthenticationFailure(request, response, failed);
}
}
在successfulAuthentication内部,将认证信息存储到了SecurityContext中。并调用了loginSuccess方法,这就是 常见的“**记住我”功能!**此功能具体应用,咱们后续再研究!
初步实现认证功能
根据以上的源码分析,接下来完成SpringSecurity使用数据库数据完成认证的实现
创建UserDetailsService
// 接口
public interface UserService extends UserDetailsService {
public void save(SysUser user);
public List<SysUser> findAll();
public Map<String, Object> toAddRolePage(Integer id);
public void addRoleToUser(Integer userId, Integer[] ids);
}
编写loadUserByUsername业务
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser sysUser = userDao.findByName(username);
if(sysUser==null){
//若用户名不对,直接返回null,表示认证失败。
return null;
}
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
List<SysRole> roles = sysUser.getRoles();
for (SysRole role : roles) {
authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
}
//最终需要返回一个SpringSecurity的UserDetails对象,{noop}表示不加密认证。
return new User(sysUser.getUsername(), "{noop}"+sysUser.getPassword(), authorities);
}
UserDetailsService接口的实现类
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Autowired
private RoleService roleService;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Override
public void save(SysUser user) {
user.setPassword(user.getPassword());
userDao.save(user);
}
}
SpringSecurity.xml指定认证使用的业务对象
修改之前写死的认证用户信息的来源
<!--设置Spring Security认证用户信息的来源-->
<security:authentication-manager>
<security:authentication-provider user-service-ref="userServiceImpl">
</security:authentication-provider>
</security:authentication-manager>
加密认证
在IOC容器中提供加密对象
SpringSecurity.xml
<!--加密对象-->
<bean id="passwordEncoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<!--设置Spring Security认证用户信息的来源-->
<security:authentication-manager>
<security:authentication-provider user-service-ref="userServiceImpl">
<!--指定认证使用的加密对象-->
<security:password-encoder ref="passwordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>
修改认证方法
去掉{noop},该方法可以让我们的密码不加密
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser sysUser = userDao.findByName(username);
if(sysUser==null){
//若用户名不对,直接返回null,表示认证失败。
return null;
}
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
List<SysRole> roles = sysUser.getRoles();
for (SysRole role : roles) {
authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
}
//最终需要返回一个SpringSecurity的UserDetails对象,{noop}表示不加密认证。
return new User(sysUser.getUsername(), sysUser.getPassword(), authorities);
}
修改实现类
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Autowired
private RoleService roleService;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Override
public void save(SysUser user) {
//对密码进行加密,然后再入库
user.setPassword(passwordEncoder.encode(user.getPassword()));
userDao.save(user);
}
}