授权原理
如果我们想要控制用户权限,需要两部分数据
1:配置资源访问需要的权限
2.用户拥有的权限
本质上,权限控制实际上就是控制哪些url能否访问.
SpringSecurity授权
内置权限表达式
ExpressionUrlAuthorizationConfigurer包含了所有表达式
URL权限控制
/**
* 自定义权限不足信息
* @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;
}
}
Method安全表达式
提供四种注解:@PreAuthorize , @PostAuthorize , @PreFilter , @PostFilter .
开启注解配置:
在方法上使用注解
@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)即:基于角色的权限控制。模型中有几个关键的术语:
用户:系统接口及访问的操作者
权限:能够访问某接口或者做某操作的授权资格
角色:具有一类相同操作权限的总称
源码分析
过滤器加载流程
1.springboot启动时,会加载spring.factories文件,其中有SpringSecurity的过滤链配置信息
2.SecurityFilterAutoConfiguration类
3.SecurityAutoConfiguration类
4.WebSecurityEnablerConfiguration类
@EnableWebSecurity注解有两个作用:1.加载了WebSecurityConfiguration配置类, 配置安全认证策略。2.加载了AuthenticationConfiguration, 配置了认证信息。
5.WebSecurityConfiguration类
认证流程分析
代码跟踪
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);
}