一只会飞的旺旺
文章93
标签72
分类8
SpringSecurity授权与源码分析

SpringSecurity授权与源码分析

小卖铺上新啦!ChatGPT账号大甩卖! 一键直达

授权原理

image-20221012135724547

如果我们想要控制用户权限,需要两部分数据

1:配置资源访问需要的权限

2.用户拥有的权限

本质上,权限控制实际上就是控制哪些url能否访问.

SpringSecurity授权

内置权限表达式

ExpressionUrlAuthorizationConfigurer包含了所有表达式

image-20221012140635225

URL权限控制

image-20221012141221556

/**
 * 自定义权限不足信息
 * @author pengwangwang
 * @date 2022/10/12 14:10
 **/
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=utf-8");
        httpServletResponse.getWriter().write("权限不足,请联系管理员");
    }
}

自定义Bean授权

/**
 * 自定义授权类
 *
 * @author pengwangwang
 * @date 2022/10/12 14:16
 **/
@Component
public class MyAuthorizationService {

    /**
     * 检查用户是否有对应的访问权限
     *
     * @param authentication 登录用户
     * @param request        请求对象
     * @return
     */
    public boolean check(Authentication authentication, HttpServletRequest request) {
        User user = (User) authentication.getPrincipal();
        // 获取用户所有权限
        Collection<GrantedAuthority> authorities = user.getAuthorities();
        // 获取用户名
        String username = user.getUsername();
        // 如果用户名为admin,则不需要认证
        if (username.equalsIgnoreCase("admin")) {
            return true;
        } else {
            // 循环用户的权限, 判断是否有ROLE_ADMIN权限, 有返回true
            for (GrantedAuthority authority : authorities) {
                String role = authority.getAuthority();
                if ("ROLE_ADMIN".equals(role)) {
                    return true;
                }
            }
        }
        return false;
    }


}

image-20221012142058924

Method安全表达式

提供四种注解:@PreAuthorize , @PostAuthorize , @PreFilter , @PostFilter .

开启注解配置:

image-20221012142757613

在方法上使用注解

    @RequestMapping("/findAll")
    @PreAuthorize("hasRole('ROLE_ADMIN')") // 指定角色才能访问
    public String findAll(Model model) {
        List<User> userList = userService.list();
        model.addAttribute("userList", userList);
        return "user_list";
    }
  • @ProAuthorize: 注解适合进入方法前的权限验证
  • @PostAuthorize: 在方法执行后再进行权限验证,适合验证带有返回值的权限,returnObject : 代表return返回的值
  • @PreFilter: 可以用来对集合类型的参数进行过滤, 将不符合条件的元素剔除集合
  • @PostFilter: 可以用来对集合类型的返回值进行过滤, 将不符合条件的元素剔除集合

RBAC权限模型简介

RBAC权限模型(Role-Based Access Control)即:基于角色的权限控制。模型中有几个关键的术语:

用户:系统接口及访问的操作者

权限:能够访问某接口或者做某操作的授权资格

角色:具有一类相同操作权限的总称

image-20221012144007485

源码分析

过滤器加载流程

image-20221012144745168

1.springboot启动时,会加载spring.factories文件,其中有SpringSecurity的过滤链配置信息

image-20221012145637699

2.SecurityFilterAutoConfiguration类

image-20221012151957617

3.SecurityAutoConfiguration类

image-20221012152343337

4.WebSecurityEnablerConfiguration类

image-20221012152516407

@EnableWebSecurity注解有两个作用:1.加载了WebSecurityConfiguration配置类, 配置安全认证策略。2.加载了AuthenticationConfiguration, 配置了认证信息。

5.WebSecurityConfiguration类

image-20221012152759076

认证流程分析

image-20221012153038461

代码跟踪

UsernamePasswordAuthenticationFilter

@Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {
        //1.检查是否是post请求
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        //2.获取用户名和密码
        String username = obtainUsername(request);
        username = (username != null) ? username : "";
        username = username.trim();
        String password = obtainPassword(request);
        password = (password != null) ? password : "";
        //3.创建AuthenticationToken,未认证状态
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);
        // 4.调用AuthenticationManager进行认证
        return this.getAuthenticationManager().authenticate(authRequest);
    }

UsernamePasswordAuthenticationToken

    public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
        super(authorities);
        this.principal = principal; // 设置用户名
        this.credentials = credentials; // 设置密码
        super.setAuthenticated(false); // 设置认证状态为未认证
    }

AuthenticationManager–>ProviderManager–>AbstractUserDetailsAuthenticationProvider

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
            return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
        });
        // 1.获取用户名
        String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
        //2.尝试从缓存中获取
        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;

            try {
                //3.检索User
                user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            } catch (UsernameNotFoundException var6) {
                this.logger.debug("User '" + username + "' not found");
                if (this.hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
                }

                throw var6;
            }

            Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }

        try {
            //4.认证前检查user状态
            this.preAuthenticationChecks.check(user);
            //5.附加认证认证检查
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        } catch (AuthenticationException var7) {
            if (!cacheWasUsed) {
                throw var7;
            }

            cacheWasUsed = false;
            user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        }
        
        //6.认证后检查user状态
        this.postAuthenticationChecks.check(user);
        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;
        if (this.forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }

        //7.创建认证成功的UsernamePasswordAuthenticationToken并将认证状态设置为true
        return this.createSuccessAuthentication(principalToReturn, authentication, user);
    }

retrieveUser方法

protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    this.prepareTimingAttackProtection();

    try {
        // 调用自定义UserDetailsService的loadUserByUserName的方法
        UserDetails loadedUser = this.getUserDetailsService的loadUserByUserName的方法().loadUserByUsername(username);
        if (loadedUser == null) {
            throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
        } else {
            return loadedUser;
        }
    } catch (UsernameNotFoundException var4) {
        this.mitigateAgainstTimingAttack(authentication);
        throw var4;
    } catch (InternalAuthenticationServiceException var5) {
        throw var5;
    } catch (Exception var6) {
        throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
    }
}

additionalAuthenticationChecks方法

protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    if (authentication.getCredentials() == null) {
        this.logger.debug("Failed to authenticate since no credentials provided");
        throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
    } else {
        //1.提取前端代码
        String presentedPassword = authentication.getCredentials().toString();
        //2.与数据库中的密码进行比对
        if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
            this.logger.debug("Failed to authenticate since password does not match stored value");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }
    }
}

AbstractAuthenticationProcessingFilter–doFilter方法

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        if (!requiresAuthentication(request, response)) {
            chain.doFilter(request, response);
            return;
        }
        try {
            //1.调用子类方法
            Authentication authenticationResult = attemptAuthentication(request, response);
            if (authenticationResult == null) {
                // return immediately as subclass has indicated that it hasn't completed
                return;
            }
            //2.session策略验证
            this.sessionStrategy.onAuthentication(authenticationResult, request, response);
            // Authentication success
            if (this.continueChainBeforeSuccessfulAuthentication) {
                chain.doFilter(request, response);
            }
            //3.成功身份验证
            successfulAuthentication(request, response, chain, authenticationResult);
        }
        catch (InternalAuthenticationServiceException failed) {
            this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
            unsuccessfulAuthentication(request, response, failed);
        }
        catch (AuthenticationException ex) {
            // Authentication failed
            unsuccessfulAuthentication(request, response, ex);
        }
    }

successfulAuthentication方法

protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
      Authentication authResult) throws IOException, ServletException {
   //1.将认证的用户放入SecurityContext中
   SecurityContextHolder.getContext().setAuthentication(authResult);
   if (this.logger.isDebugEnabled()) {
      this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
   }
   //2.检查是不是记住我
   this.rememberMeServices.loginSuccess(request, response, authResult);
   if (this.eventPublisher != null) {
      this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
   }
   //3. 调用自定义MyAuthenticationService的onAuthenticationSuccess方法
   this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
微信支付码 微信支付
支付宝支付码 支付宝支付