认证:你是谁
授权:你能做什么
鉴权:你能不能做(你可以用钱买房子,但是你的钱不够)
权限控制:同意/不同意做
Authorization的or抽象成author,是人做的事,即授权,Authentication是认证
3. SpringSecurity和shirospringsecurity
- .功能全面
- .专为web开发设计
- .旧版本不能脱离web使用
- .重量级框架
- .是构建oauth的基础
shiro
- 轻量级
- 通用性好
- 可脱离web使用
public interface Authentication extends Principal, Serializable {
//获取用户的权限
Collection extends GrantedAuthority> getAuthorities();
//获取凭证,相当于获取密码
Object getCredentials();
//获取详细的用户信息,ip之类的
Object getDetails();
//获取用户信息,例如含有用户名的实体类
Object getPrincipal();
//是否已经认证了
boolean isAuthenticated();
//更改认证状态
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
5. GrantedAuthority
public interface GrantedAuthority extends Serializable {
String getAuthority();
}
6. SecurityContextHolder
public class SecurityContextHolder {
public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
public static final String MODE_GLOBAL = "MODE_GLOBAL";
public static final String SYSTEM_PROPERTY = "spring.security.strategy";
//从系统属性中获取策略
private static String strategyName = System.getProperty(SYSTEM_PROPERTY);
private static SecurityContextHolderStrategy strategy;
private static int initializeCount = 0;
static {
initialize();
}
private static void initialize() {
//如果没有给定策略,设置默认策略
if (!StringUtils.hasText(strategyName)) {
// Set default
strategyName = MODE_THREADLOCAL;
}
//如果是本地策略,new一个ThreadLocalSecurityContextHolderStrategy对象
if (strategyName.equals(MODE_THREADLOCAL)) {
strategy = new ThreadLocalSecurityContextHolderStrategy();
}
else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
}
else if (strategyName.equals(MODE_GLOBAL)) {
strategy = new GlobalSecurityContextHolderStrategy();
}
else {
//尝试使用自定义策略
// Try to load a custom strategy
try {
Class> clazz = Class.forName(strategyName);
Constructor> customStrategy = clazz.getConstructor();
strategy = (SecurityContextHolderStrategy) customStrategy.newInstance();
}
catch (Exception ex) {
ReflectionUtils.handleReflectionException(ex);
}
}
initializeCount++;
}
}
public interface SecurityContextHolderStrategy {
void clearContext();
SecurityContext getContext();
void setContext(SecurityContext context);
SecurityContext createEmptyContext();
}
final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
private static final ThreadLocal contextHolder = new ThreadLocal<>();
public void clearContext() {
contextHolder.remove();
}
public SecurityContext getContext() {
SecurityContext ctx = contextHolder.get();
if (ctx == null) {
ctx = createEmptyContext();
contextHolder.set(ctx);
}
return ctx;
}
public void setContext(SecurityContext context) {
Assert.notNull(context, "only non-null SecurityContext instances are permitted");
contextHolder.set(context);
}
public SecurityContext createEmptyContext() {
return new SecurityContextImpl();
}
}
package org.springframework.security.core.context;
import org.springframework.security.core.Authentication;
import java.io.Serializable;
public interface SecurityContext extends Serializable {
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
}
7. UserDetails和UserDetailsService
package org.springframework.security.core.userdetails;
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
package org.springframework.security.core.userdetails;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import java.io.Serializable;
import java.util.Collection;
public interface UserDetails extends Serializable {
Collection extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
8. AuthenticationManager
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
9. ProviderManager
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
private static final Log logger = LogFactory.getLog(ProviderManager.class);
private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
private List providers = Collections.emptyList();
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
//认证管理可认证
private AuthenticationManager parent;
private boolean eraseCredentialsAfterAuthentication = true;
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();
for (AuthenticationProvider provider : getProviders()) {
//如果当前provider不支持认证这种类型,跳过
if (!provider.supports(toTest)) {
continue;
}
if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
}
try {
//使用当前认证提供者尝试认证
result = provider.authenticate(authentication);
//如果结果不为空则跳出循坏
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException e) {
prepareException(e, authentication);
throw e;
} catch (AuthenticationException e) {
lastException = e;
}
}
//如果认证结果为空且parent不为空
if (result == null && parent != null) {
// Allow the parent to try.
try {
//让父亲去认证
result = parentResult = parent.authenticate(authentication);
}
catch (ProviderNotFoundException e) {
}
catch (AuthenticationException e) {
lastException = parentException = e;
}
}
//
if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
((CredentialsContainer) result).eraseCredentials();
}
if (parentResult == null) {
eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] { toTest.getName() },
"No AuthenticationProvider found for {0}"));
}
if (parentException == null) {
prepareException(lastException, authentication);
}
throw lastException;
}
}
10. 认证流程
思路
既然认证是通过AuthenticationProvider进行的,那么去找它的实现类;DaoAuthenticationProvider是AuthenticationProvider的实现类,AuthenticationProvider继承AbstractUserDetailsAuthenticationProvider抽象类,而AbstractUserDetailsAuthenticationProvider实现AuthenticationProvider,所以重点关注AbstractUserDetailsAuthenticationProvider这个抽象类
public abstract class AbstractUserDetailsAuthenticationProvider implements
AuthenticationProvider, InitializingBean, MessageSourceAware {
protected final Log logger = LogFactory.getLog(getClass());
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private UserCache userCache = new NullUserCache();
private boolean forcePrincipalAsString = false;
protected boolean hideUserNotFoundExceptions = true;
private UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks();
private UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks();
private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
protected abstract void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
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");
doAfterPropertiesSet();
}
//重点关注这个方法,因为AuthenticationProvider调用这个方法进行验证
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//判断authentication是否属于UsernamePasswordAuthenticationToken这种类型的
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
() -> messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.onlySupports",
"only UsernamePasswordAuthenticationToken is supported"));
// Determine username
//获取用户名
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
: authentication.getName();
boolean cacheWasUsed = true;
//从缓存获取用户信息
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException notFound) {
logger.debug("User '" + username + "' not found");
if (hideUserNotFoundExceptions) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
else {
throw notFound;
}
}
Assert.notNull(user,
"retrieveUser returned null - a violation of the interface contract");
}
try {
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
}
catch (AuthenticationException exception) {
if (cacheWasUsed) {
// There was a problem, so try again after checking
// we're using latest data (i.e. not from the cache)
cacheWasUsed = false;
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
else {
throw exception;
}
}
postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
// 就是UserDetails
Object principalToReturn = user;
if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
return createSuccessAuthentication(principalToReturn, authentication, user);
}
protected Authentication createSuccessAuthentication(Object principal,
Authentication authentication, UserDetails user) {
// Ensure we return the original credentials the user supplied,
// so subsequent attempts are successful even with encoded passwords.
// Also ensure we return the original getDetails(), so that future
// authentication events after cache expiry contain the details
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
principal, authentication.getCredentials(),
authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
return result;
}
protected void doAfterPropertiesSet() throws Exception {
}
public UserCache getUserCache() {
return userCache;
}
public boolean isForcePrincipalAsString() {
return forcePrincipalAsString;
}
public boolean isHideUserNotFoundExceptions() {
return hideUserNotFoundExceptions;
}
protected abstract UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
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));
}
private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
public void check(UserDetails user) {
if (!user.isAccountNonLocked()) {
logger.debug("User account is locked");
throw new LockedException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.locked",
"User account is locked"));
}
if (!user.isEnabled()) {
logger.debug("User account is disabled");
throw new DisabledException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.disabled",
"User is disabled"));
}
if (!user.isAccountNonExpired()) {
logger.debug("User account is expired");
throw new AccountExpiredException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.expired",
"User account has expired"));
}
}
}
private class DefaultPostAuthenticationChecks implements UserDetailsChecker {
public void check(UserDetails user) {
if (!user.isCredentialsNonExpired()) {
logger.debug("User account credentials have expired");
throw new CredentialsExpiredException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.credentialsExpired",
"User credentials have expired"));
}
}
}
}
11. Filter回顾
过程
请求到达Servlet,然后依次执行过滤器,若过滤器的拦截的url相同,则默认按名称排序执行, 以栈的形式先进后出执行
作用: 创建SecurityContext和销毁SecurityContext13. 核心过滤器之UsernamePasswordAuthenticationFilter
作用:生成UsernamePasswordAuthenticationFilter
UsernamePasswordAuthenticationFilter继承AbstractAuthenticationProcessingFilter抽象类
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 = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
private boolean postonly = true;
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());
}
//从request获取username
String username = obtainUsername(request);
//从request获取password
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
//生成token
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
@Nullable
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(passwordParameter);
}
@Nullable
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(usernameParameter);
}
}
- BasicAuthenticationFilter
public class BasicAuthenticationFilter extends OncePerRequestFilter {
private AuthenticationEntryPoint authenticationEntryPoint;
private AuthenticationManager authenticationManager;
private RememberMeServices rememberMeServices = new NullRememberMeServices();
private boolean ignoreFailure = false;
private String credentialsCharset = "UTF-8";
private BasicAuthenticationConverter authenticationConverter = new BasicAuthenticationConverter();
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
final boolean debug = this.logger.isDebugEnabled();
try {
UsernamePasswordAuthenticationToken authRequest = authenticationConverter.convert(request);
if (authRequest == null) {
chain.doFilter(request, response);
return;
}
String username = authRequest.getName();
if (authenticationIsRequired(username)) {
//开始认证
Authentication authResult = this.authenticationManager
.authenticate(authRequest);
if (debug) {
this.logger.debug("Authentication success: " + authResult);
}
SecurityContextHolder.getContext().setAuthentication(authResult);
this.rememberMeServices.loginSuccess(request, response, authResult);
onSuccessfulAuthentication(request, response, authResult);
}
}
catch (AuthenticationException failed) {
SecurityContextHolder.clearContext();
if (debug) {
this.logger.debug("Authentication request for failed: " + failed);
}
this.rememberMeServices.loginFail(request, response);
onUnsuccessfulAuthentication(request, response, failed);
if (this.ignoreFailure) {
chain.doFilter(request, response);
}
else {
this.authenticationEntryPoint.commence(request, response, failed);
}
return;
}
chain.doFilter(request, response);
}
}
15. AnonymousAuthenticationFilter
public class AnonymousAuthenticationFilter extends GenericFilterBean implements InitializingBean {
private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource();
private String key;
private Object principal;
private List authorities;
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
if (SecurityContextHolder.getContext().getAuthentication() == null) {
//获取不到认证信息,直接创建新的认证信息
SecurityContextHolder.getContext().setAuthentication(
createAuthentication((HttpServletRequest) req));
if (logger.isDebugEnabled()) {
logger.debug("Populated SecurityContextHolder with anonymous token: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'");
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'");
}
}
chain.doFilter(req, res);
}
protected Authentication createAuthentication(HttpServletRequest request) {
//匿名用户用AnonymousAuthenticationToken替代UsernamePasswordAuthenticationToken
AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key,
principal, authorities);
auth.setDetails(authenticationDetailsSource.buildDetails(request));
return auth;
}
}
16. 完整流程
- WebAsyncManagerIntegrationFilter
在 Callable 上填充 SecurityContext,提供 SecurityContext 和 Spring Web 的 WebAsyncManager 之 间的集成。 - (重要)SecurityContextPersistenceFilter
在请求之前使用从配置的 SecurityContextRepository 获取的信息填充 SecurityContextHolder,并在请求完成并清除上下文持有者后将其存储回存储库 - org.springframework.security.web.header.HeaderWriterFilter
过滤器实现以向当前响应添加标头。 添加某些启用浏览器保护的标头可能很有用。 像 X-frame-Options、X-XSS-Protection 和 X-Content-Type-Options。 - (重要)UsernamePasswordAuthenticationFilter
处理身份验证表单提交。 在 Spring Security 3.0 之前调用 AuthenticationProcessingFilter。
登录表单必须向此过滤器提供两个参数:用户名和密码。 要使用的默认参数名称包含在静态字段 SPRING_SECURITY_FORM_USERNAME_KEY 和 SPRING_SECURITY_FORM_PASSWORD_KEY 中。 也可以通过设置 usernameParameter 和 passwordParameter 属性来更改参数名称。
默认情况下,此过滤器响应 URL /login。 - DefaultLoginPageGeneratingFilter
在用户未配置登录页面的情况下,用于名称空间配置的内部使用。 配置代码将在链中插入这个过滤器。 仅当重定向用于登录页面时才有效。 - DefaultLogoutPageGeneratingFilter
生成默认的注销页面。 - RequestCacheAwareFilter
如果缓存的请求与当前请求匹配,则负责重新构造已保存的请求 - SecurityContextHolderAwareRequestFilter
一个过滤器,它用实现servlet API安全方法的请求包装器填充ServletRequest。 - AnonymousAuthenticationFilter
检测SecurityContextHolder中是否没有Authentication对象,并在需要时使用一个对象填充它 - SessionManagementFilter
检测用户从请求开始时就已经通过了身份验证,如果已经通过了身份验证,则调用配置的SessionAuthenticationStrategy来执行任何与会话相关的活动,例如激活会话固定保护机制或检查多个并发登录。 - ExceptionTranslationFilter
处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException - (重要)FilterSecurityInterceptor
通过过滤器实现执行HTTP资源的安全处理。
思路:FilterSecurityInterceptor是filter,就一定有dofilter这个方法,所以先找dofilter
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied";
private FilterInvocationSecuritymetadataSource securitymetadataSource;
private boolean observeoncePerRequest = true;
public void init(FilterConfig arg0) {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//这里是把request和response做了类型转换
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
public FilterInvocationSecuritymetadataSource getSecuritymetadataSource() {
return this.securitymetadataSource;
}
public SecuritymetadataSource obtainSecuritymetadataSource() {
return this.securitymetadataSource;
}
public void setSecuritymetadataSource(FilterInvocationSecuritymetadataSource newSource) {
this.securitymetadataSource = newSource;
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if ((fi.getRequest() != null)&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)&& observeOncePerRequest) {
//只执行一次处理
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
//第一次处理
// first time this request being called, so perform security checking
if (fi.getRequest() != null && observeOncePerRequest) {
//相当于做标记,有这个标记说明处理过了
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
}
public abstract class AbstractSecurityInterceptor implements InitializingBean,ApplicationEventPublisherAware, MessageSourceAware {
//访问决策管理器
private AccessDecisionManager accessDecisionManager;
private AfterInvocationManager afterInvocationManager;
private RunAsManager runAsManager = new NullRunAsManager();
private boolean alwaysReauthenticate = false;
private boolean rejectPublicInvocations = false;
private boolean validateConfigAttributes = true;
private boolean publishAuthorizationSuccess = false;
protected InterceptorStatusToken beforeInvocation(Object object) {
//这里的object是FilterInvocation的对象
Assert.notNull(object, "Object was null");
//从securitymetadataSource获取权限列表,securitymetadataSource在构造FilterSecurityInterceptor时传入
Collection attributes = this.obtainSecuritymetadataSource().getAttributes(object);
if (attributes == null || attributes.isEmpty()) {
//最好把rejectPublicInvocations设置为true,因为该用户没有权限,不应该让用户访问下去
if (rejectPublicInvocations) {
throw new IllegalArgumentException(
"Secure object invocation "
+ object
+ " was denied as public invocations are not allowed via this interceptor. "
+ "This indicates a configuration error because the "
+ "rejectPublicInvocations property is set to 'true'");
}
publishEvent(new PublicInvocationEvent(object));
return null; // no further work post-invocation
}
//获取登录用户的认证信息,不能为空
if (SecurityContextHolder.getContext().getAuthentication() == null) {
credentialsNotFound(messages.getMessage(
"AbstractSecurityInterceptor.authenticationNotFound",
"An Authentication object was not found in the SecurityContext"),
object, attributes);
}
//是否需要认证,是则去认证
Authentication authenticated = authenticateIfRequired();
// Attempt authorization
try {
//这里是重点,调用访问决策管理器去决定让该用户访问该url(权限)
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,accessDeniedException));
throw accessDeniedException;
}
//不抛AccessDeniedException就是允许访问
if (debug) {
logger.debug("Authorization successful");
}
}
protected void finallyInvocation(InterceptorStatusToken token) {
if (token != null && token.isContextHolderRefreshRequired()) {
if (logger.isDebugEnabled()) {
logger.debug("Reverting to original Authentication: "
+ token.getSecurityContext().getAuthentication());
}
SecurityContextHolder.setContext(token.getSecurityContext());
}
}
//这个方法与beforeInvocation同理,区别在于beforeInvocation在进入controller方法之前做决策,afterInvocation在controller方法返回之后做决策,根据filterchain的效果推出的
protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
if (token == null) {
// public object
return returnedObject;
}
finallyInvocation(token); // continue to clean in this method for passivity
if (afterInvocationManager != null) {
// Attempt after invocation handling
try {
returnedObject = afterInvocationManager.decide(token.getSecurityContext()
.getAuthentication(), token.getSecureObject(), token
.getAttributes(), returnedObject);
}
catch (AccessDeniedException accessDeniedException) {
AuthorizationFailureEvent event = new AuthorizationFailureEvent(
token.getSecureObject(), token.getAttributes(), token
.getSecurityContext().getAuthentication(),
accessDeniedException);
publishEvent(event);
throw accessDeniedException;
}
}
return returnedObject;
}
}
public interface AccessDecisionManager {
void decide(Authentication authentication, Object object,
Collection configAttributes) throws AccessDeniedException,
InsufficientAuthenticationException;
boolean supports(ConfigAttribute attribute);
boolean supports(Class> clazz);
}
public class Affirmativebased extends AbstractAccessDecisionManager {
//需要决策器列表
public Affirmativebased(List> decisionVoters) {
super(decisionVoters);
}
public void decide(Authentication authentication, Object object,Collection configAttributes) throws AccessDeniedException {
//拒绝的票数
int deny = 0;
//遍历所有访问决策器
for (AccessDecisionVoter voter : getDecisionVoters()) {
//决策结果
int result = voter.vote(authentication, object, configAttributes);
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
//同意票
return;
case AccessDecisionVoter.ACCESS_DENIED:
//拒绝票
deny++;
break;
default:
break;
}
}
//拒绝票大于0拒绝该用户访问
if (deny > 0) {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
}
}
//访问决策器接口,这里只看实现类RoleVoter public interface AccessDecisionVoter{ //同意 int ACCESS_GRANTED = 1; //弃权 int ACCESS_ABSTAIN = 0; //拒绝 int ACCESS_DENIED = -1; boolean supports(ConfigAttribute attribute); boolean supports(Class> clazz); int vote(Authentication authentication, S object, Collectionattributes); }
public class RoleVoter implements AccessDecisionVoter18. 权限过滤的第二种方式
通过查看源码的方式可知,权限过滤有两种方法。一种是上面这种基于filter的,另一种是注解方式
这里说注解方式
看GlobalMethodSecurityConfiguration注释可知怎么自定义
数据源(SecuritymetadataSource)思路
我们知道通过@PreAuthorize配置访问方法所需的权限,而启用该注解的方式是使用@EnableGlobalMethodSecurity。从@EnableGlobalMethodSecurity的文档说明可以找到配置类GlobalMethodSecurityConfiguration 。该配置类有一个bean返回的是MethodSecurityInterceptor的对象,
MethodSecurityInterceptor继承AbstractSecurityInterceptor,通过前面我们知道,AbstractSecurityInterceptor是FilterSecurityInterceptor的父类,FilterSecurityInterceptor是基于过滤器的而MethodSecurityInterceptor是基于注解的。MethodSecurityInterceptor有一个invoke方法,这个方法调用了AbstractSecurityInterceptor的beforeInvocation方法,beforeInvocation会先通过数据源获取attributes,然后用attributes作为访问决策管理器的decide的参数去做决策,没有抛AccessDeniedException异常就算通过,这就是权限过滤的大概流程
通过debug可知,MethodSecurityInterceptor使用数据源PrePostAnnotationSecuritymetadataSource,
PrePostAnnotationSecuritymetadataSource继承自AbstractMethodSecuritymetadataSource
通过debug可知,MethodSecurityInterceptor使用Affirmativebased(一票通过)
访问决策器(AccessDecisionVoter)通过debug可知,MethodSecurityInterceptor的访问决策器有PreInvocationAuthorizationAdviceVoter,
PreInvocationAuthorizationAdviceVoter通过自身的属性PreInvocationAuthorizationAdvice的before方法活的投票结果
思路:一切都从AbstractSecurityInterceptor抽象类开始,若基于过滤器就参考FilterSecurityInterceptor,基于注解就参考MethodSecurityInterceptor,二选一即可。无论选哪种,有3个必要的组件,数据源、访问决策管理器和访问决策器
技巧- 基于过滤器不想重写FilterSecurityInterceptor可以这样做
@Override
protected void configure(HttpSecurity http) throws Exception {.setAccessDecisionManager(new Affirmativebased(Arrays.asList(new AnnotationPermissionVoter())));
//这个不是基于session一定要关了
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/logout").permitAll()
//需要认证
.anyRequest().authenticated();
//开启表单登录
http.formLogin();
http.authorizeRequests().withObjectPostProcessor(new ObjectPostProcessor() {
@Override
public O postProcess(O o) {
//配置数据源
o.setSecuritymetadataSource(new MyFilterSecuritymetadataSource());
//配置访问决策管理器
o.setAccessDecisionManager(getAffirmativebased());
return o;
}
});
}
- 基于注解可以这样写
@Resource
MethodSecurityInterceptor methodSecurityInterceptor;
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置数据源
methodSecurityInterceptor.setAccessDecisionManager(getAccessDecisionManager());
//配置访问决策管理器
methodSecurityInterceptor.setSecuritymetadataSource(getSecuritymetadataSource());
//这个不是基于session一定要关了
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/logout").permitAll()
//需要认证
.anyRequest().authenticated();
//开启表单登录
http.formLogin();
}
20. 自定义认证
思路:写过滤器解析放在请求头的token,禁用session,



