SpringSecurity除了身份认证之外,还支持授权管理,其授权维度分为两类:role、authority
编写demo
- 测试用api
package com.learn.security.api.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author PC
* 权限测试Controller
*/
@RestController("authController.v1")
@RequestMapping("/v1/auth")
public class AuthController {
@GetMapping("/admin")
public String admin(){
return "hello admin";
}
@GetMapping("/role-admin")
public String roleAdmin(){
return "hello ROLE_admin";
}
@GetMapping("/pub")
public String pub(){
return "hello pub";
}
}
- UserDetailsServiceImpl#loadUserByUsername中添加授权代码
@Override
public UserDetails loadUserByUsername(String loginName) throws UsernameNotFoundException {
// 1. 查询用户
Login loginInfo = loginService.getLoginInfo(loginName);
if (loginInfo == null) {
//这里找不到必须抛异常
throw new UsernameNotFoundException("User " + loginName + " was not found in db");
}
//2. 获取并加密密码
Assert.isTrue(StringUtils.isNotEmpty(loginInfo.getPassword()), "password deletion");
String password = passwordEncoder.encode(loginInfo.getPassword());
//3. 赋予user账户一个或多个权限,用逗号分隔,测试可以使用用户名,正式需添加权限字段
List<GrantedAuthority> grantedAuthorityList = AuthorityUtils.commaSeparatedStringToAuthorityList(loginName);
return new User(loginName, password, grantedAuthorityList);
}
- 添加未授权页面un-auth.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>未授权</title>
</head>
<body>
<h1>您未获得授权,请重新<a href="/login.html">登录</a></h1>
</body>
</html>
- SecurityAutoConfiguration中添加权限配置
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
//自定义登录页面
.formLogin()
//登陆页面设置
.loginPage("/login.html")
//登录url设置
.loginProcessingUrl("/user/login")
//登录成功后跳转的路径,如果希望跳回原路径,alwaysUse不填或填false
.defaultSuccessUrl("/success.html")
//允许访问
.permitAll()
//未获得授权跳转的页面
.and().exceptionHandling().accessDeniedPage("/un-auth.html")
//设置认证权限
.and().authorizeRequests()
//该接口需要admin权限
.antMatchers("/v1/auth/admin").hasAuthority("admin")
//该接口需要ROLE_admin角色
.antMatchers("/v1/auth/role-admin").hasRole("admin")
//该接口为公开接口
.antMatchers("/v1/auth/pub").permitAll()
.anyRequest().authenticated()
//关闭csrf防护
.and().csrf().disable();
return http.build();
}
测试
- 访问http://127.0.0.1:8888/v1/auth/pub,可正常访问,不用登录
- 访问http://127.0.0.1:8888/v1/auth/role-admin,用admin用户登录
会跳到未授权页面
- 继续访问http://127.0.0.1:8888/v1/auth/role-admin,用ROLE_admin重新登录
可正常返回
角色前缀变更
默认前缀分析
通过测试,我们可以看到,在SecurityAutoConfiguration中,仅配置了antMatchers("/v1/auth/role-admin").hasRole("admin"),并没有指定ROLE_admin,那么这个前缀是在哪配置的呢?
进入hasRole方法,其代码如下
public ExpressionUrlAuthorizationConfigurer<H>.ExpressionInterceptUrlRegistry hasRole(String role) {
return this.access(ExpressionUrlAuthorizationConfigurer.hasRole(ExpressionUrlAuthorizationConfigurer.this.rolePrefix, role));
}
从中可以发现,有一个this.rolePrefix,ExpressionUrlAuthorizationConfigurer有一个构造方法,设置了rolePrefix的值,其代码如下
public ExpressionUrlAuthorizationConfigurer(ApplicationContext context) {
String[] grantedAuthorityDefaultsBeanNames = context.getBeanNamesForType(GrantedAuthorityDefaults.class);
if (grantedAuthorityDefaultsBeanNames.length == 1) {
GrantedAuthorityDefaults grantedAuthorityDefaults = (GrantedAuthorityDefaults)context.getBean(grantedAuthorityDefaultsBeanNames[0], GrantedAuthorityDefaults.class);
this.rolePrefix = grantedAuthorityDefaults.getRolePrefix();
} else {
this.rolePrefix = "ROLE_";
}
this.REGISTRY = new ExpressionInterceptUrlRegistry(context);
}
从中可以发现,如果没有进行特殊配置,rolePrefix就被赋值为ROLE_
修改前缀
分析出了默认前缀的来源,接下来我们就可以自定义前缀,从代码中可知,如果想要自定义前缀,需要初始化GrantedAuthorityDefaults类,而前缀在代码中写死也不太方便,因此还需要支持在yml中配置
- SecurityConfigProperties
package com.learn.security.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @author PC
* Security配置属性
*/
@Data
@Configuration
@ConfigurationProperties("cus.security")
public class SecurityConfigProperties {
private String rolePrefix = "ROLE_";
}
- SecurityAutoConfiguration
@Bean
public GrantedAuthorityDefaults grantedAuthorityDefaults(){
return new GrantedAuthorityDefaults(securityConfigProperties.getRolePrefix());
}
- application.yml
cus:
security:
role_prefix: role_
测试
- 为了测试自定义前缀,数据库中添加role_admin用户
- 访问http://127.0.0.1:8888/v1/auth/role-admin,使用ROLE_admin登录
- 重新访问http://127.0.0.1:8888/v1/auth/role-admin,使用role_admin登录
使用授权注解
@Secured
若想使用注解,需要在启动类中先开启securedEnabled
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityApplication.class, args);
}
}
判断是否有角色,另外需要注意的是这里匹配的字符串需要添加前缀“role_”(注意,此处是我的自定义前缀,如果是默认的,则为ROLE_),如下所示,仅有当包含任一角色时才能访问
@Secured(value = {"admin"})
@GetMapping("/admin")
public String admin(){
return "hello admin";
}
@Secured(value = {"role_admin"})
@GetMapping("/role-admin")
public String roleAdmin(){
return "hello ROLE_admin";
}
上述代码和在初始授权时在SecurityAutoConfiguration类中配置的其实是一样的效果,多余的配置代码可以删除
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
//自定义登录页面
.formLogin()
//登陆页面设置
.loginPage("/login.html")
//登录url设置
.loginProcessingUrl("/user/login")
//登录成功后跳转的路径,如果希望跳回原路径,alwaysUse不填或填false
.defaultSuccessUrl("/success.html")
//允许访问
.permitAll()
//配置登出
.and().logout().logoutUrl("/logout").logoutSuccessUrl("/login.html").permitAll()
//未获得授权跳转的页面
.and().exceptionHandling().accessDeniedPage("/un-auth.html")
//设置认证权限
.and().authorizeRequests()
.anyRequest().authenticated()
//关闭csrf防护
.and().csrf().disable();
return http.build();
}
测试
- 访问http://127.0.0.1:8888/v1/auth/admin,用role_admin登录
无权访问
访问http://127.0.0.1:8888/v1/auth/role-admin
参考资料
[1]gitee项目仓库地址