(十二)Spring Security默认实现

  1. 更多的配置 (重写configure方法)实现的自定义认证和授权(重写用户名密码认证和模仿eladmin的token认证)和自定义授权
  2. Spring Security在SpringBoot中的自动装配原理
  3. SecurityContext

ps:自定义授权,包含注解式开发和java-configure开发

参考

Spring Security用户认证和权限控制(默认实现)

Spring Security用户认证和权限控制(自定义实现)

认证和鉴权

认证和鉴权分别对应authentication/authorizationSpring SecurityShiro都涉及到了这两个词

通俗点说

  • 认证就是登录的过程。服务器根据你传递的用户名,密码或者其他形式的参数查询是否存在这个用户,如果存在说明认证成功

  • 鉴权就是判断你是否由权力访问资源的流程。你想看某个视频,只有vip用户才能看,但是你当前登录的账户不是vip,所以你不能看。判断能不能看这个视频(是否有权限访问指定的资源)的过程就是鉴权

Spring Security的过滤器链

Spring Security集成了Spring MVCSpring Security通过创建一系列Filter(过滤器)来实现认证和授权

集成Spring Security

  1. 导入Maven依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    
  2. 创建WebSecurityConfigurerAdapter的子类并添加@EnableWebSecurity注解

    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    }
    

至此,启动项目后,Spring Security为程序添加了许多过滤器用于认证和鉴权

Spring Security中内置的过滤器

Spring Security中有许多内置的过滤器(这里只简单介绍两个)

在尝试自定义认证和授权流程之前应该先掌握内置的过滤器是如何工作的

其中我认为比较重要的内置过滤器是UsernamePasswordAuthenticationFilterFilterSecurityInterceptor(它是过滤器,不是拦截器)

Authentication

在了解过滤器之前先了解一下Authentication

此类是“认证主体”,这个类的包含了用户是否认证成功,用户的信息等信息

public interface Authentication extends Principal, Serializable {

    // 获取权限
	Collection<? extends GrantedAuthority> getAuthorities();

	Object getCredentials();

	Object getDetails();

	Object getPrincipal();
	
    // 返回认证主体是否已经被认证过
	boolean isAuthenticated();

    // 这只认证主体是否成功通过认证
	void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

UsernamePasswordAuthenticationFilter

这个过滤器提供用户名,密码方式认证服务

这个过滤器拦截了/login请求并校验用户名,密码。如果认证成功(用户名,密码都对了)就创建一个Authentication对象;认证失败就抛AuthenticationException

  1. 默认只拦截 POST方式的/login请求,如果不符合直接执行下面的过滤器(chain.doFilter(request, response);
  2. 获取请求中的用户名和密码,并创建一个Authentication对象(实际类型是UsernamePasswordAhtienticationToken),这个对象是没有被认证的“认证主体”
  3. 使用成员变量AuthenticationManagerauthenticate()方法进行认证
  4. AuthenticationManager遍历持有的AuthenticationProvider对象,使用能认证此AhthenticationAuthenticationProvider进行认证
  5. AbstractUserDetailsAuthenticationProvider先使用缓存获取user对象,如果获取不到就用持有的UserDetialsServicePasswordEncoderDB中获取User对象并加密密码再封装起来
  6. 拿到user对象后校验对象是否过期,密码是否正确等。没问题后创建一个新的AuthenticationToken作为已经被认证的“认证主体”
  7. AuthenticationToken放到SecurityContext中(SecurityContext以后再说)

上述流程中重要的部分是

  • 过滤器本身和其认证逻辑
  • AuthenticationManager对象。它持有各种AuthenticationProvider,可以完成对不同类型Authentication的认证
  • AuthenticationProvider持有的UserDetialsServicePasswordEncoder。他们的作用是创建UserDetials对象为以后创建被认证的Authentication做铺垫
/* AbstractAuthenticationProcessingFilter 是UsernamePasswordAuthenticationFilter的父类 */
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {

    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;

    // 如果请求不符合要求就直接放过
    if (!requiresAuthentication(request, response)) {
        chain.doFilter(request, response);
        return;
    }
    Authentication authResult;
    try {
        // 认证流程在这里,如果认证失败会抛异常
        authResult = attemptAuthentication(request, response);
        if (authResult == null) {
            return;
        }
    }
    catch (AuthenticationException failed) {
        return;
    }

    if (continueChainBeforeSuccessfulAuthentication) {
        chain.doFilter(request, response);
    }

    // 将Authentication放进SecurityContext中
    successfulAuthentication(request, response, chain, authResult);

}

/* UsernamePasswordAuthenticationFilter */
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());
    }

    String username = obtainUsername(request);
    String password = obtainPassword(request);

    // 创建一个未认证的AuthenticationToken
    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
        username, password);
	// 调用AuthenticationManager进行认证
    return this.getAuthenticationManager().authenticate(authRequest);
}

/* ProviderManager */
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    Class<? extends Authentication> toTest = authentication.getClass();

    Authentication result = null;

    // 遍历持有的AuthenticationProvider,如果有AuthenticationProvider能认证Authentication就认证
    for (AuthenticationProvider provider : getProviders()) {
        // AuthenticationProvider是否能认证当前类型的Authentication
        if (!provider.supports(toTest)) {
            continue;
        }
        try {
            // 认证
            result = provider.authenticate(authentication);
            if (result != null) {
                break;
            }
        } catch (AuthenticationException e) {
        }
	}
    if (result != null) {
        return result;
    }
}

/* AbstractUserDetailsAuthenticationProvider */
public Authentication authenticate(Authentication authentication) throws AuthenticationException {

    String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
        : authentication.getName();

    // 尝试从缓存中获取User对象
    boolean cacheWasUsed = true;
    UserDetails user = this.userCache.getUserFromCache(username);

    // 缓存中获取不到就重新获取并缓存起来
    if (user == null) {
        cacheWasUsed = false;

        try {
            // 调用UserDetailsService获取user对象并使用PasswordEncoder加密密码
            user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
        }
        catch (UsernameNotFoundException notFound) {
        }
    }

    try {
        // 前置检查
        preAuthenticationChecks.check(user);
        // 检查密码是否正确
        additionalAuthenticationChecks(user,
                                       (UsernamePasswordAuthenticationToken) authentication);
    }
    catch (AuthenticationException exception) {
    }

    // 后置检查。前后置检查都在检查user是否已经过期
    postAuthenticationChecks.check(user);

    // 将user缓存起来
    if (!cacheWasUsed) {
        this.userCache.putUserInCache(user);
    }

    Object principalToReturn = user;

	// 创建一个被认证的Authentication(能执行到这里还没抛异常说明user认证成功了)
    return createSuccessAuthentication(principalToReturn, authentication, user);
}

FilterSecurityInterceptor

这个过滤器被放在过滤器链的最后面,控制鉴权流程,用于检查此次请求是否有权限访问资源,如果不能就抛AccessException

  1. SecurityContext中获取Authentication对象,如果没有认证就认证一下(调用AuthenticationManager认证)
  2. 获取当前请求资源所需要的权限
  3. 调用AccessDecisionManager持有的投票器进行投票,根据投票结果判定请求携带的Authentication是否有权限访问资源。判定标准根据AccessDecisionManager不同的实现类决定。例如一票否决,一票通过,多数服从少数等标准

上述流程中重要的部分是

  • 过滤器本身
  • AccessDecisionManager和投票器

ps:通常鉴权流程交给这个过滤器就足够了,无需创建新的鉴权过滤器

AnonymousAuthenticationFilter

匿名的“权限主体”过滤器

  1. 如果你没有经过认证就访问被保护起来的资源,这个过滤器会检测到SecurityContext中没有Authentication对象,并为其创建一个AnonymousAuthenticationToken类型的Authentication并放进SecurityContext
  2. 匿名的“认证主体”只有ROLE_ANONYMOUS权限。在访问被保护起来的资源时会被FilterSecurityInterceptor判定为没有权限访问资源的
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值