授权原理

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);
}