前言
- 从Servlet 3.0开始,在HttpServletRequest中就支持登录,登出等操作了
- 所以SpringSecurity也有一个过滤器会对HttpServletRequest进行包装,以支持通过HttpServletRequest进行登录登出等操作
- ServletApiConfigurer正是SecurityContextHolderAwareRequestFilter对应的配置类,也是默认开启的配置类之一
- 此配置类中可以调用的方法很少,只有rolePrefix(...)和configure(...)
1.1 rolePrefix(...)
java
复制代码
public ServletApiConfigurer<H> rolePrefix(String rolePrefix) {
this.securityContextRequestFilter.setRolePrefix(rolePrefix);
return this;
}
- configure(...):由于此过滤器是为了让HttpServletRequest支持认证等操作,所以说这里就设置了认证相关的对象
java
复制代码
public void configure(H http) {
//设置局部认证管理器
this.securityContextRequestFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
//设置身份认证入口点
ExceptionHandlingConfigurer<H> exceptionConf = http.getConfigurer(ExceptionHandlingConfigurer.class);
AuthenticationEntryPoint authenticationEntryPoint = (exceptionConf != null)
? exceptionConf.getAuthenticationEntryPoint(http) : null;
this.securityContextRequestFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
//设置登出处理器
LogoutConfigurer<H> logoutConf = http.getConfigurer(LogoutConfigurer.class);
List<LogoutHandler> logoutHandlers = (logoutConf != null) ? logoutConf.getLogoutHandlers() : null;
this.securityContextRequestFilter.setLogoutHandlers(logoutHandlers);
//设置认证对象解析器
AuthenticationTrustResolver trustResolver = http.getSharedObject(AuthenticationTrustResolver.class);
if (trustResolver != null) {
this.securityContextRequestFilter.setTrustResolver(trustResolver);
}
//设置角色前缀
ApplicationContext context = http.getSharedObject(ApplicationContext.class);
if (context != null) {
String[] grantedAuthorityDefaultsBeanNames = context.getBeanNamesForType(GrantedAuthorityDefaults.class);
if (grantedAuthorityDefaultsBeanNames.length == 1) {
GrantedAuthorityDefaults grantedAuthorityDefaults = context
.getBean(grantedAuthorityDefaultsBeanNames[0], GrantedAuthorityDefaults.class);
this.securityContextRequestFilter.setRolePrefix(grantedAuthorityDefaults.getRolePrefix());
}
}
this.securityContextRequestFilter = postProcess(this.securityContextRequestFilter);
http.addFilter(this.securityContextRequestFilter);
}
2. SecurityContextHolderAwareRequestFilter
- 此过滤器的doFilter(...)方法很简单,就是通过HttpServletRequestFactory包装Request
java
复制代码
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(
//包装Request
this.requestFactory.create((HttpServletRequest) req
, (HttpServletResponse) res), res);
}
2.1 HttpServletRequestFactory
- HttpServletRequestFactory:用于包装 HttpServletRequest 的内部接口
java
复制代码
interface HttpServletRequestFactory {
HttpServletRequest create(HttpServletRequest request, HttpServletResponse response);
}
- HttpServletRequestFactory只有一个实现:HttpServlet3RequestFactory
- HttpServlet3RequestFactory:这个实现类的核心就在于将HttpServletRequest包装为了Servlet3SecurityContextHolderAwareRequestWrapper
- Servlet3SecurityContextHolderAwareRequestWrapper:支持Servlet 3.0的登录,登出,异步等操作的包装类
java
复制代码
@Override
public HttpServletRequest create(HttpServletRequest request, HttpServletResponse response) {
return new Servlet3SecurityContextHolderAwareRequestWrapper(request, this.rolePrefix, response);
}
- SpringSecurity中不仅仅只有这个过滤器将HttpServletRequest进行了包装,像HeaderWriterFilter也包装了,这就是装饰模式的体现
2.2 Servlet3Security...Wrapper
- Servlet3SecurityContextHolderAwareRequestWrapper对于HttpServletRequest中有关认证的方法都进行了重写
- authenticate(...):重新进行认证
- login(...):进行认证
- logout(...):进行登出
- ...
2.2.1 authenticate(...)
- authenticate(...):重新进行认证,如若没有进行认证那就重定向到登录页
java
复制代码
public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {
//获得身份认证入口点
//一般情况都是重定向到登录页的
AuthenticationEntryPoint entryPoint = HttpServlet3RequestFactory.this.authenticationEntryPoint;
if (entryPoint == null) {
HttpServlet3RequestFactory.this.logger.debug(
"authenticationEntryPoint is null, so allowing original HttpServletRequest to handle authenticate");
return super.authenticate(response);
}
if (isAuthenticated()) {
return true;
}
//执行
entryPoint.commence(this, response,
new AuthenticationCredentialsNotFoundException("User is not Authenticated"));
return false;
}
- 这里是通过SecurityContextHolder.getContext().getAuthentication().getPrincipal()是否为空来判断认证情况的
java
复制代码
/**
* 获得认证对象中主体(Principal) * 一般情况都是User或者其子类
*/
public Principal getUserPrincipal() {
Authentication auth = getAuthentication();
if ((auth == null) || (auth.getPrincipal() == null)) {
return null;
}
return auth;
}
/**
* 获得认证对象
* 注意:匿名用户视为未登陆
*/
private Authentication getAuthentication() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return (!this.trustResolver.isAnonymous(auth)) ? auth : null;
}
2.2.2 login(...)
java
复制代码
public void login(String username, String password) throws ServletException {
if (isAuthenticated()) {
throw new ServletException("Cannot perform login for '" + username + "' already authenticated as '"
+ getRemoteUser() + "'");
}
AuthenticationManager authManager = HttpServlet3RequestFactory.this.authenticationManager;
if (authManager == null) {
HttpServlet3RequestFactory.this.logger.debug(
"authenticationManager is null, so allowing original HttpServletRequest to handle login");
super.login(username, password);
return;
}
//调用局部认证管理器进行认证
Authentication authentication = getAuthentication(authManager, username, password);
//创建安全上下文,并放入线程级别的安全上下文策略中
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
}
2.2.3 logout(...)
java
复制代码
@Override
public void logout() throws ServletException {
List<LogoutHandler> handlers = HttpServlet3RequestFactory.this.logoutHandlers;
if (CollectionUtils.isEmpty(handlers)) {
HttpServlet3RequestFactory.this.logger
.debug("logoutHandlers is null, so allowing original HttpServletRequest to handle logout");
super.logout();
return;
}
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
//调用登出处理器,进行登出
//比如说清空Csrf的处理器
for (LogoutHandler handler : handlers) {
handler.logout(this, this.response, authentication);
}
}
2.2.4 isUserInRole(...)
- isUserInRole(...):判断当前用户是否拥有指定角色
java
复制代码
public boolean isUserInRole(String role) {
return isGranted(role);
}
/**
* 判断当前用户是否拥有某个角色
* @param role
* @return
*/
private boolean isGranted(String role) {
Authentication auth = getAuthentication();
//添加角色前缀
if (this.rolePrefix != null && role != null && !role.startsWith(this.rolePrefix)) {
role = this.rolePrefix + role;
}
if ((auth == null) || (auth.getPrincipal() == null)) {
return false;
}
Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
if (authorities == null) {
return false;
}
for (GrantedAuthority grantedAuthority : authorities) {
if (role.equals(grantedAuthority.getAuthority())) {
return true;
}
}
return false;
}