prefacio
Spring Security
Soporte de control de permisos a nivel de método. Con base en este mecanismo, podemos agregar anotaciones de permisos a cualquier método en cualquier capa, y el método agregado a la anotación se protegerá automáticamente Spring Security
, permitiendo el acceso solo a usuarios específicos, para lograr el propósito del control de permisos. la anotación de permiso existente Si no está satisfecho, también podemos personalizar
Inicio rápido
- Primero agregue la dependencia de seguridad de la siguiente manera
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2. Luego cree una nueva clase de configuración de seguridad
Spring Security
De forma predeterminada, las anotaciones están deshabilitadas. Para habilitar las anotaciones, debe anotar la WebSecurityConfigurerAdapter
clase heredada @EnableMethodSecurity
y AuthenticationManager
definirla comoBean。
@EnableWebSecurity
@Configuration
@EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
Vemos que @EnableGlobalMethodSecurity
hay tres campos respectivamente prePostEnabled 、securedEnabled、jsr250Enabled
, cada uno de los cuales codifica un soporte de anotación, y el predeterminado es false,true为开启
. Luego, hablemos sobre el soporte de estas tres anotaciones generales una por una.
El efecto de prePostEnabled = true es habilitar las anotaciones @PreAuthorize y @PostAuthorize de Spring Security.
La función de secureEnabled = true es habilitar la anotación @Secured de Spring Security.
La función de jsr250Enabled = true es habilitar la anotación @RoleAllowed
Para un uso e integración más detallados, consulte mis dos artículos
Fácil de usar SpringBoot+SpringSecurity+JWT Combate de control de permisos RESTfulAPI real
Establecer autenticación de autorización en el método
Anotaciones JSR-250
Cumplir con las anotaciones estándar JSR-250
Anotaciones principales
- @DenyAll
- @rolespermitidos
- @PermitAll
Aquí @DenyAll
y @PermitAll
creo que sobra decirlo, representan rechazo y aprobación.
@RolesAllowed
Ejemplo de uso
@RolesAllowed("ROLE_VIEWER")
public String getUsername2() {
//...
}
@RolesAllowed({
"USER", "ADMIN" })
public boolean isValidUsername2(String username) {
//...
}
Se puede acceder a los métodos que representan anotaciones siempre que tengan permisos de USUARIO, ADMINISTRADOR. El prefijo ROLE_ se puede omitir aquí y la autoridad real puede ser ROLE_ADMIN
En términos de función y uso, es @Secured
exactamente lo mismo que
anotación secureEnabled
nota principal
@Asegurado
-
Anotaciones para Spring Security
@Secured
. La anotación especifica la lista de funciones del método de acceso, y al menos una función se especifica en la lista -
@Secured
Especifique la seguridad en el método, requiriendo roles/permisos, etc. Solo los usuarios con los roles/permisos correspondientes pueden llamar a estos métodos. Si alguien intenta llamar a un método, pero no tiene los roles/permisos requeridos, se generará una excepción de acceso denegado.
Por ejemplo:
@Secured("ROLE_VIEWER")
public String getUsername() {
SecurityContext securityContext = SecurityContextHolder.getContext();
return securityContext.getAuthentication().getName();
}
@Secured({
"ROLE_DBA", "ROLE_ADMIN" })
public String getUsername2() {
//...
}
@Secured("ROLE_VIEWER")
Indica que solo ROLE_VIEWER
los usuarios con el rol pueden acceder getUsername()
al método.
@Secured({ "ROLE_DBA", "ROLE_ADMIN" })
Indica que el usuario tiene " ROLE_DBA", "ROLE_ADMIN"
cualquiera de los dos roles y puede acceder getUsername2
al método.
Otro punto es que @Secured no admite expresiones Spring EL
anotación prePostEnabled
Después de activarlo, es bastante potente para admitir expresiones Spring EL. Se lanza una AccessDeniedException si no hay permiso para acceder al método.
nota principal
-
@PreAuthorize
-- Adecuado para validar la autorización antes de ingresar al método -
@PostAuthorize
-- Comprobar el método de autorización antes de que se ejecute y puede afectar el valor de retorno del método de ejecución
3. @PostFilter
--Ejecutar después de ejecutar el método, y aquí puede llamar al valor de retorno del método, luego filtrar o procesar o modificar el valor de retorno y regresar
@PreFilter
-- Ejecutar antes de que se ejecute el método, y aquí puede llamar a los parámetros del método y luego filtrar, procesar o modificar los valores de los parámetros
Uso de la anotación @PreAuthorize
@PreAuthorize("hasRole('ROLE_VIEWER')")
public String getUsernameInUpperCase() {
return getUsername().toUpperCase();
}
@PreAuthorize("hasRole('ROLE_VIEWER')") es equivalente a @Secured("ROLE_VIEWER").
El mismo @Secured({“ROLE_VIEWER”,”ROLE_EDITOR”})
también puede ser reemplazado por: @PreAuthorize(“hasRole(‘ROLE_VIEWER') or hasRole(‘ROLE_EDITOR')”)
.
Además, podemos usar expresiones en los parámetros del método:
Ejecutado antes de que se ejecute el método, aquí puede llamar a los parámetros del método, y también puede obtener el valor del parámetro. Aquí, se usa la función de reflejo del nombre del parámetro de JAVA8. Si no hay JAVA8, también puede usar Spring @P de Security para marcar parámetros, o utilice @Param de Spring Data para marcar los parámetros.
//无java8
@PreAuthorize("#userId == authentication.principal.userId or hasAuthority(‘ADMIN’)")
void changePassword(@P("userId") long userId ){
}
//有java8
@PreAuthorize("#userId == authentication.principal.userId or hasAuthority(‘ADMIN’)")
void changePassword(long userId ){
}
Esto significa que changePassword
antes de que se ejecute el método, se juzga si el valor del parámetro del método userId es igual al userId del usuario actual guardado en el principal, o si el usuario actual tiene la autoridad ROLE_ADMIN, y si uno de los dos coincidencias, se puede acceder al método.
@PostAuthorize uso de anotaciones
Después de ejecutar el método, se puede obtener el valor de retorno del método y se puede determinar el resultado final de la autorización de acuerdo con el método (ya sea para permitir el acceso o no):
@PostAuthorize
("returnObject.username == authentication.principal.nickName")
public CustomUser loadUserDetail(String username) {
return userRoleRepository.loadUserByUserName(username);
}
loadUserDetail
En el código anterior, solo se permite el acceso cuando el nombre de usuario en el valor de retorno del método es el mismo que el nombre de usuario del usuario que ha iniciado sesión actualmente.
Tenga en cuenta que si EL es falso, entonces el método también se ha ejecutado y se puede revertir. La variable EL returnObject representa el objeto devuelto.
Se utilizan las anotaciones @PreFilter y @PostFilter
Spring Security proporciona una @PreFilter
anotación para filtrar los parámetros entrantes:
@PreFilter("filterObject != authentication.principal.username")
public String joinUsernames(List<String> usernames) {
return usernames.stream().collect(Collectors.joining(";"));
}
Cuando el subelemento de los nombres de usuario sea diferente del nombre de usuario del usuario que ha iniciado sesión actualmente, consérvelo; cuando el subelemento de los nombres de usuario sea el mismo que el nombre de usuario del usuario que ha iniciado sesión actualmente, elimínelo. Por ejemplo, el nombre de usuario del usuario actual es zhangsan, y el valor de los nombres de usuario en este momento es {"zhangsan", "lisi", "wangwu"}
, luego de filtrar por @PreFilter, el valor de los nombres de usuario realmente pasados es{"lisi", "wangwu"}
Si el método de ejecución contiene varios parámetros de tipo Colección, filterObject no sabrá qué parámetro de Colección filtrar. En este punto, debe agregar el atributo filterTarget para especificar el nombre del parámetro específico:
@PreFilter
(value = "filterObject != authentication.principal.username",
filterTarget = "usernames")
public String joinUsernamesAndRoles(
List<String> usernames, List<String> roles) {
return usernames.stream().collect(Collectors.joining(";"))
+ ":" + roles.stream().collect(Collectors.joining(";"));
}
Del mismo modo, también podemos usar la Colección que @PostFilter
se ha anotado 返回
para filtrar:
@PostFilter("filterObject != authentication.principal.username")
public List<String> getAllUsernamesExceptCurrent() {
return userRoleRepository.getAllUsernames();
}
En este punto, filterObject representa el valor devuelto. Si se sigue el código anterior, se realizará: elimine el subelemento en el valor de retorno que es el mismo que el nombre de usuario del usuario que ha iniciado sesión actualmente.
Meta anotaciones personalizadas
Si necesitamos usar la misma anotación de seguridad en varios métodos, podemos mejorar la mantenibilidad del proyecto creando meta-anotaciones.
Por ejemplo, cree las siguientes meta-anotaciones:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('ROLE_VIEWER')")
public @interface IsViewer {
}
Luego puede agregar directamente la anotación al método correspondiente:
@IsViewer
public String getUsername4() {
//...
}
En los proyectos de producción, dado que las metaanotaciones separan la lógica empresarial y el marco de seguridad, el uso de metaanotaciones es una muy buena opción.
Usar anotaciones de seguridad en las clases
Si todos los métodos de una clase se aplican con la misma anotación de seguridad, entonces la anotación de seguridad debe elevarse al nivel de clase en este momento:
@Service
@PreAuthorize("hasRole('ROLE_ADMIN')")
public class SystemService {
public String getSystemYear(){
//...
}
public String getSystemDate(){
//...
}
}
El código anterior se da cuenta de que se requiere el permiso ROLE_ADMIN para acceder a los métodos getSystemYear y getSystemDate.
Aplicar múltiples anotaciones de seguridad a los métodos
Cuando una anotación de seguridad no puede satisfacer nuestras necesidades, también se pueden aplicar múltiples anotaciones de seguridad:
@PreAuthorize("#username == authentication.principal.username")
@PostAuthorize("returnObject.username == authentication.principal.nickName")
public CustomUser securedLoadUserDetail(String username) {
return userRoleRepository.loadUserByUserName(username);
}
En este momento, Spring Security ejecutará la política de seguridad de @PreAuthorize antes de ejecutar el método y ejecutará la política de seguridad de @PostAuthorize después de ejecutar el método.
Resumir
Basado en nuestra experiencia, aquí hay dos consejos:
-
De forma predeterminada, el proxy Spring AOP implementa el uso de anotaciones de seguridad en los métodos, lo que significa que si llamamos al método 2 en la misma clase que usa anotaciones de seguridad en el método 1, las anotaciones de seguridad en el método 2 no serán válidas.
-
El contexto de Spring Security está vinculado a subprocesos, lo que significa que el contexto de seguridad no se pasará a subprocesos secundarios.
public boolean isValidUsername4(String username) {
// 以下的方法将会跳过安全认证
this.getUsername();
return true;
}