栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

spring security

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

spring security

1. 认证,授权,鉴权,权限控制

认证:你是谁
授权:你能做什么
鉴权:你能不能做(你可以用钱买房子,但是你的钱不够)
权限控制:同意/不同意做

2. Authorization,Authentication的记法

Authorization的or抽象成author,是人做的事,即授权,Authentication是认证

3. SpringSecurity和shiro

springsecurity

  • .功能全面
  • .专为web开发设计
  • .旧版本不能脱离web使用
  • .重量级框架
  • .是构建oauth的基础

shiro

  • 轻量级
  • 通用性好
  • 可脱离web使用
4. 核心组件Authentication

public interface Authentication extends Principal, Serializable {
	//获取用户的权限
    Collection 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 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 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相同,则默认按名称排序执行, 以栈的形式先进后出执行

12. 核心过滤器之 SecurityContextPersistenceFilter
  作用: 创建SecurityContext和销毁SecurityContext
13. 核心过滤器之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);
	}
}
  1. 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. 完整流程





17. 权限认证
  • 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


思路: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,
			Collection attributes);
}
public class RoleVoter implements AccessDecisionVoter {

	private String rolePrefix = "ROLE_";
	public String getRolePrefix() {
		return rolePrefix;
	}
	//可以设置角色前缀
	public void setRolePrefix(String rolePrefix) {
		this.rolePrefix = rolePrefix;
	}
	//这就是为什么角色要以ROLE_开头
	public boolean supports(ConfigAttribute attribute) {
		if ((attribute.getAttribute() != null)
				&& attribute.getAttribute().startsWith(getRolePrefix())) {
			return true;
		}
		else {
			return false;
		}
	}

	public boolean supports(Class clazz) {
		return true;
	}
	//关键代码
	public int vote(Authentication authentication, Object object,
			Collection attributes) {
		//如果认证信息为空,拒绝
		if (authentication == null) {
			return ACCESS_DENIED;
		}
		//先赋值弃权
		int result = ACCESS_ABSTAIN;
		//
		Collection authorities = extractAuthorities(authentication);
		//这里的attribute是角色 ,遍历当前用户的角色
		for (ConfigAttribute attribute : attributes) {
			if (this.supports(attribute)) {
				//如果支持当前类型,把弃权改为拒绝	
				result = ACCESS_DENIED;
				// Attempt to find a matching granted authority
				//遍历访问url需要的角色
				for (GrantedAuthority authority : authorities) {
					//判断角色是否相等
					if (attribute.getAttribute().equals(authority.getAuthority())) {
						return ACCESS_GRANTED;
					}
				}
			}
		}
		return result;
	}

	Collection extractAuthorities(
			Authentication authentication) {
		return authentication.getAuthorities();
	}
}
 
18. 权限过滤的第二种方式 

通过查看源码的方式可知,权限过滤有两种方法。一种是上面这种基于filter的,另一种是注解方式
这里说注解方式
看GlobalMethodSecurityConfiguration注释可知怎么自定义

思路
我们知道通过@PreAuthorize配置访问方法所需的权限,而启用该注解的方式是使用@EnableGlobalMethodSecurity。从@EnableGlobalMethodSecurity的文档说明可以找到配置类GlobalMethodSecurityConfiguration 。该配置类有一个bean返回的是MethodSecurityInterceptor的对象,
MethodSecurityInterceptor继承AbstractSecurityInterceptor,通过前面我们知道,AbstractSecurityInterceptor是FilterSecurityInterceptor的父类,FilterSecurityInterceptor是基于过滤器的而MethodSecurityInterceptor是基于注解的。MethodSecurityInterceptor有一个invoke方法,这个方法调用了AbstractSecurityInterceptor的beforeInvocation方法,beforeInvocation会先通过数据源获取attributes,然后用attributes作为访问决策管理器的decide的参数去做决策,没有抛AccessDeniedException异常就算通过,这就是权限过滤的大概流程

数据源(SecuritymetadataSource)

通过debug可知,MethodSecurityInterceptor使用数据源PrePostAnnotationSecuritymetadataSource,
PrePostAnnotationSecuritymetadataSource继承自AbstractMethodSecuritymetadataSource

访问决策管理器(AccessDecisionManager)

通过debug可知,MethodSecurityInterceptor使用Affirmativebased(一票通过)

访问决策器(AccessDecisionVoter)

通过debug可知,MethodSecurityInterceptor的访问决策器有PreInvocationAuthorizationAdviceVoter,
PreInvocationAuthorizationAdviceVoter通过自身的属性PreInvocationAuthorizationAdvice的before方法活的投票结果

19. 自定义权限过滤

思路:一切都从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,

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号