gestión de autoridad
1. El concepto central de la autorización
En el blog anterior en la columna, el proceso de autenticación se describe en detalle. Una vez que sabemos que la autenticación es exitosa, la información del usuario actualmente conectado se guardará en el objeto. Hay un método en el objeto de autenticación para devolver el Authentication
permiso getAuthorities()
. información del usuario conectado actualmente. , es decir, el usuario actual tiene información de permisos. El valor de retorno de este método es Collection<? extends GrantedAuthority>
que cuando se requiere un juicio de permiso, se llamará al método correspondiente para juzgar de acuerdo con la información de permiso devuelta por el conjunto. (Y cómo el contexto obtiene esta autenticación después de la autenticación también se explica en detalle en el blog anterior; obténgalo a través de la clase SecurityContextHolder)
Mire el marcador de posición del valor de retorno, ¿ GrantedAuthority
cómo debemos entender el valor de retorno? ¿Es un rol o un permiso?
Para la gestión de autorizaciones, generalmente usamos el RBAC
modelo familiar, donde R puede llamarse Resources
o llamarse, Roles
es decir, podemos referirnos a la autorización comoGestión de permisos basada en rolesyBasado en la gestión de derechos de recursos. Desde una perspectiva de diseño, los roles y los permisos son dos cosas completamente diferentes: los permisos son algunas operaciones específicas y los roles son una colección de ciertos permisos. Tales como: READ_BOOK y ROLE_ADMIN son completamente diferentes. Entonces, cuál es el valor de retorno depende de la situación del diseño comercial:
- El diseño basado en permisos de roles es:
用户<=>角色<=>资源
el retorno de la relación entre los tres es el del usuario角色
. - El diseño basado en permisos de recursos es:
用户<=>权限<=>资源
el retorno de la relación entre los tres es el del usuario权限
- El diseño basado en roles y permisos de recursos es:
用户<=>角色<=>权限<=>资源
volver denominados colectivamente como usuarios权限
.
¿Por qué pueden denominarse colectivamente permisos? Debido a que no hay mucha diferencia entre roles y permisos a nivel de código, todos son permisos. En Spring Security en particular, los roles y permisos se manejan básicamente de la misma manera (ambos son cadenas). La única diferencia es que Spring Security agregará automáticamente un ROLE_
prefijo a los roles en muchos casos, pero los permisos no se agregarán automáticamente (esto se deriva de la implementación interna de Spring Security).
2. Estrategia de gestión de autoridades
En el desarrollo real, la autenticación siempre se fija durante el proyecto, mientras que la gestión de permisos es generalmente variable y los desarrolladores deben comprenderla.
Hay dos tipos principales de estrategias de gestión de autoridad proporcionadas en Spring Security :
¿A qué recursos del sistema se puede acceder? <=> (http, URL, método)
-
Gestión de derechos basada en filtros (
FilterSecurityInterceptor
)- La gestión de autoridad basada en filtros se utiliza principalmente para interceptar solicitudes HTTP y, después de la intercepción, la verificación de autoridad se realiza de acuerdo con la dirección de solicitud HTTP.
-
Gestión de derechos basada en AOP (
MethodSecurityInterceptor
)- La administración de permisos basada en AOP se usa principalmente para tratar problemas de permisos a nivel de método. Cuando es necesario llamar a un método, la operación se intercepta a través de AOP y luego se juzga si el usuario tiene la autoridad pertinente.
Expresiones de permiso (SpEL Spring EL)
Antes de explicar las dos estrategias de administración de permisos, expliquemos la expresión de permiso integrada de Spring Security (expresión SpEL). Se utiliza principalmente para configurar permisos, podemos configurar los permisos requeridos a través de expresiones de permiso en la URL solicitada o método de acceso (anotación). Las siguientes son las expresiones de permiso integradas de Spring Security:
expresión | efecto |
---|---|
hasRole(Rol de cadena) | Si el usuario actual tiene el rol especificado |
hasAnyRole(Cadena…roles) | Si el usuario actual tiene alguno de los roles especificados |
hasAuthority(Autoridad de cadena) | Si el usuario actual tiene los permisos especificados |
hasAnyAuthority(String…autoridades) | Si el usuario actual tiene alguno de los permisos especificados |
permitirTodo() | Permitir todas las solicitudes/llamadas |
denyAll() | Denegar todas las solicitudes/llamadas |
esAnónimo() | Si el usuario actual es un usuario anónimo |
está autenticado() | Si el usuario actual se ha autenticado con éxito |
esRecuérdame() | Si el usuario actual inicia sesión automáticamente a través de RememberMe |
está completamente autenticado () | Si el usuario actual no es anónimo ni inició sesión automáticamente a través de RememberMe |
hasPermission(Objetivo de objeto,Permiso de objeto) | Si el usuario actual tiene los permisos especificados para el objetivo especificado |
hasPermission(Object targetId,String targetType,Permiso de objeto) | Si el usuario actual tiene los permisos especificados para el objetivo especificado |
obtener autenticación () | Obtener el objeto de autenticación |
autenticación | Este es el objeto de autenticación obtenido del SecurityContext, que ya está autenticado |
principal | Representa el principal de inicio de sesión actual Principal |
En términos generales, las expresiones de permiso integradas proporcionadas por Spring Security son suficientes.
1. Gestión de autoridad basada en URL (filtro)
La gestión de autoridad basada en la dirección URL se realiza principalmente a través del filtro FilterSecurityInterceptor. Si el desarrollador configura la administración de permisos en función de la dirección URL, FilterSecurityInterceptor se agregará automáticamente a la cadena de filtros de Spring Security, y la cadena de filtros interceptará la solicitud y luego se analizará si el usuario actual tiene el permiso requerido. para la solicitud, si no está presente, se lanza una excepción.
uso básico
(Al crear el proyecto SpringBoot, accidentalmente creé la versión 6.1. No quiero cambiarla. La siguiente prueba se basa en la última versión. Se han habilitado algunos métodos. La siguiente es la última versión del código y la versión 6.1 SpringSecurity no tiene @Configuration en la meta-anotación de la anotación @EnableWebSecurity, así que recuerde agregarlo afuera, esto es un hoyo).
clase de configuración
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public UserDetailsService userDetailsService(){
return new InMemoryUserDetailsManager(
User.withUsername("root").password("{noop}123").roles("ADMIN","USER").authorities("READ_INFO").build(),
User.withUsername("admin").password("{noop}123").roles("USER").build(),
User.withUsername("myz").password("{noop}123").authorities("READ_INFO").build()
);
}
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http,@Autowired UserDetailsService userDetailsService) throws Exception {
AuthenticationManagerBuilder builder = http.getSharedObject(AuthenticationManagerBuilder.class);
builder.userDetailsService(userDetailsService);
return builder.build();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.authorizeRequests()
.requestMatchers("/user").hasRole("USER")
.requestMatchers("/getInfo").hasAuthority("READ_INFO")
.requestMatchers("/admin").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin(configurer->{
})
.csrf(configurer->configurer.disable())
.build();
}
}
controlador de prueba
@RestController
public class TestController {
@GetMapping("/user")
public String user(){
return "user ok!";
}
@GetMapping("/admin")
public String admin(){
return "admin ok!";
}
@GetMapping("/getInfo")
public String getInfo(){
return "info ok!";
}
}
Resultados de la prueba: los usuarios que iniciaron sesión con myz/123 solo pueden acceder /getInfo
; los usuarios que iniciaron sesión con admin/123 solo pueden acceder /user
; los usuarios que iniciaron sesión con root/123 pueden acceder a las tres rutas de recursos de prueba.
2. Gestión de derechos basada en métodos (AOP)
La gestión de derechos basada en métodos se implementa principalmente a través de AOP, y Spring Security MethodSecurityInterceptor
proporciona implementaciones relacionadas a través de . La diferencia es que en la URL, FilterSecurityInterceptor
este tipo de solicitud se procesa previamente, mientras que MethodSecurityInterceptor también puede realizar un procesamiento posterior además del procesamiento previo ( equivalente a un aspecto de envoltura ). El procesamiento previo es para determinar si tiene los permisos correspondientes antes de la solicitud, y el procesamiento posterior es para realizar un filtrado secundario en los resultados de ejecución del método.El preprocesamiento y el posprocesamiento corresponden a diferentes clases de implementación.
@EnableGlobalMethodSecurity
La anotación EnableGlobalMethodSecurity se utiliza paraAnotación de permiso abiertoSí, la anotación está habilitada para usarse en el código del proyecto. El uso es el siguiente:
@EnableWebSecurity
@EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true
)
public class SecurityConfig {
}
perPostEnabled
: abra las cuatro anotaciones de permisos proporcionadas por Spring Security, @PostAuthorize, @PostFilter, @PreAuthorize y @PreFilter.securedEnabled
: habilite la anotación @Secured proporcionada por Spring Security,Esta anotación no admite expresiones de permiso。jsr250Enabled
: Abra las anotaciones proporcionadas por JSR-250, principalmente @DenyAll, @PermitAll, @RolesAll.Además, estas anotaciones no admiten expresiones de permiso.。
Los significados de las anotaciones mencionadas anteriormente se muestran en la siguiente tabla:
anotación | significado |
---|---|
@PostAutorizar | La verificación de permisos se realiza después de que se ejecuta el método de destino |
@postfiltro | Después de que el método de destino se ejecuta enresultado devueltoFiltrar |
@preautorizar | La verificación de permisos se realiza antes de que se ejecute el método de destino |
@prefiltro | Antes de que el método de destino ejecute elparámetros del métodoFiltrar |
@Asegurado | Los métodos de destino de acceso deben tener roles correspondientes |
@DenyAll | denegar todo acceso |
@PermitAll | permitir todo el acceso |
@rolespermitidos | El método de destino de acceso debe tener el rol correspondiente |
Estas anotaciones relacionadas con la administración de permisos basada en métodos generalmente son suficientes para establecerse prePostEnabled=true
, es decir, las primeras cuatro anotaciones en la tabla de versiones.
uso básico
Configuración de seguridad
@EnableWebSecurity
@EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true
)
public class SecurityConfig {
@Bean
public UserDetailsService userDetailsService(){
return new InMemoryUserDetailsManager(
User.withUsername("root")
.password("{noop}123")
// .authorities("READ_INFO")
.roles("ADMIN","USER")
.build(),
User.withUsername("admin").password("{noop}123").roles("USER").build(),
User.withUsername("myz").password("{noop}123").authorities("READ_INFO").build()
);
}
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder builder = http.getSharedObject(AuthenticationManagerBuilder.class);
builder.userDetailsService(userDetailsService());
return builder.build();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.csrf(configurer->configurer.disable())
.build();
}
}
AuthorizeMethodController
@RestController
@RequestMapping("/hello")
public class AuthorizeMethodController {
@GetMapping("/getInfo")
public Object getInfo(){
System.out.println(SecurityContextHolder.getContext().getAuthentication().getName());
return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
@PreAuthorize("hasRole('ADMIN') and authentication.name=='root'")
@GetMapping
public String hello(){
return "hello";
}
@PreAuthorize("authentication.name==#name")
@GetMapping("/name")
public String hello(String name){
return "hello:" + name;
}
@PreFilter(value="filterObject.id%2!=0",filterTarget = "users")// filterTarget 必须是 数组 集合类型
@PostMapping("/users")
public void addUsers(@RequestBody List<User> users){
System.out.println("users = " + users);
}
@PostAuthorize("returnObject.id==1")
@GetMapping("/userId")
public User getUserById(Integer id){
return new User("myz",id);
}
@PostFilter("filterObject.id%2==0")// 用来对方法的返回值进行过滤,filterObject也是需要是集合或者是数组才行
@GetMapping("/lists")
public List<User> getAll(){
List<User> users = new ArrayList<>();
for(int i=0;i<10;++i){
users.add(new User("myz:" + i, i));
}
return users;
}
@Secured("ROLE_USER")// 只能判断角色
@GetMapping("/secured")
public User getUserByUsername(){
return new User("secured",999);
}
@Secured({
"ROLE_USER","ROLE_ADMIN"})
@GetMapping("/username")
public User getUsername(String username){
return new User(username,11111);
}
}
Usuario personalizado
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
private String name;
private Integer id;
}
Resultado de la prueba: ligeramente ~
3. El problema de la versión de la gestión de autoridad
Nota: En Spring Security versión 5.7.8, la adición de permisos de la clase de implementación UserDetails User proporcionada internamente en Spring Security depende de esto último, es decir, cuando se usa el método o para agregar permisos de objetos de usuario, los permisos reales del roles
objeto authorities
User dependen del método de uso posterior, que se deriva de la siguiente implementación interna:
Como se puede ver en el código fuente, el objeto de colección de autoridades de atributos internos se vuelve a instanciar y se asigna cada vez que se llama al método de roles o autoridades.
En la última versión 6.1.0, las autoridades de atributos internos son un objeto de colección vacío, y cuando se utilizan los métodos de roles y autoridades, los elementos se agregan a la colección de autoridades internas. se puede utilizar simultáneamente.
Pero el editor piensa que en la práctica, haremos coincidir nuestra propia clase de implementación UserDetails e implementaremos métodos personalizados, por lo que estos problemas no se encontrarán hasta cierto punto. Pero cuando use InMemoryUserDetailsManager para una prueba, debe prestar atención a esto. Después de todo, la información persistente del usuario es más significativa.