Spring Security OAuth2.0 Autenticación y autorización---Conceptos básicos

1. Conceptos básicos

1.1 ¿Qué es la autenticación?

En la era de Internet móvil, todo el mundo desliza sus teléfonos móviles todos los días. El software de uso común incluye WeChat, Alipay, Toutiao, etc. A continuación, se utiliza WeChat como ejemplo para ilustrar los conceptos básicos relacionados con la autenticación. Antes de usar WeChat para el la primera vez, debe registrarse como usuario de WeChat e ingresar su número de cuenta y contraseña para iniciar sesión en WeChat, y el proceso de ingresar la cuenta y la contraseña para iniciar sesión en WeChat es autenticación.

¿Por qué se debe autenticar el sistema?

La autenticación es para proteger los datos privados y los recursos del sistema, y ​​los usuarios solo pueden acceder a los recursos del sistema con identidades legales.

Autenticación : la autenticación del usuario es el proceso de juzgar si la identidad de un usuario es legal. Cuando un usuario accede a los recursos del sistema, el sistema requiere la verificación de la información de identidad del usuario. Solo cuando la identidad es legal, el usuario puede continuar accediendo, y si la identidad es ilegal, el acceso es denegado. Los métodos comunes de autenticación de identidad de usuario incluyen: inicio de sesión con nombre de usuario y contraseña, inicio de sesión con código QR, inicio de sesión con SMS, autenticación con huella digital, etc.

1.2, qué es una sesión

Después de autenticar al usuario, la información del usuario se puede garantizar en la sesión para evitar la autenticación para cada operación del usuario. Una sesión es un mecanismo proporcionado por el sistema para mantener el estado de inicio de sesión del usuario actual. Los métodos comunes incluyen métodos basados ​​en sesión y basados ​​en token.

1.2.1, método de autenticación basado en sesión

Su proceso de interacción es que después de que la autenticación del usuario sea exitosa, los datos relacionados con el usuario se generan en el lado del servidor y se almacenan en la sesión (sesión actual), y el session_id enviado al cliente se almacena en la cookie, de modo que el usuario puede verificar al cliente con el session_id al solicitar si hay datos de sesión en el lado del servidor, para completar la verificación legal del usuario.Cuando el usuario cierra sesión en el sistema o la sesión caduca y se destruye, el session_id del el cliente no será válido.

inserte la descripción de la imagen aquí

1.2.2 Basado en token

Su proceso de interacción es que después de que la autenticación del usuario sea exitosa, el servidor genera un token y lo envía al cliente. El cliente puede almacenarlo como una cookie o localStorage, y traer el token con cada solicitud. El servidor recibe el token y pasa la verificación La identidad del usuario puede ser confirmada.

inserte la descripción de la imagen aquí

  • El método de autenticación basado en sesión está personalizado por la especificación Servlet.El servidor necesita ocupar recursos de memoria para almacenar información de la sesión, y el cliente necesita admitir cookies;
  • El método basado en token generalmente no requiere que el servidor almacene el token y no limita el método de almacenamiento del cliente.
  • En la era actual de Internet móvil, más tipos de clientes necesitan acceder al sistema, y ​​el sistema se implementa principalmente con una arquitectura separada para el front-end y el back-end, por lo que el enfoque basado en tokens es más adecuado.

1.3 Qué es la autorización

Tome WeChat como ejemplo. Después de iniciar sesión con éxito en WeChat, los usuarios pueden usar las funciones de WeChat, como enviar sobres rojos, enviar círculos de amigos, agregar amigos, etc. Los usuarios que no han vinculado una tarjeta bancaria no pueden enviar sobres rojos. Los que vinculan una tarjeta bancaria Solo los usuarios pueden enviar sobres rojos. La función de enviar sobres rojos y la función de enviar círculos de amigos son recursos de WeChat, es decir, recursos funcionales. Los usuarios que tienen la autoridad para enviar sobres rojos pueden usar la función de enviar sobres rojos normalmente, y solo aquellos que tienen la autoridad para enviar círculos de amigos pueden usar la función de envío de amigos.La función de círculo, el proceso de controlar el uso de recursos por parte del usuario de acuerdo con la autoridad del usuario es autorización.

¿Por qué autorizar?

La autenticación es para garantizar la legitimidad de la identidad del usuario, y la autorización es para dividir los datos privados en una granularidad más fina. La autorización ocurre después de que se pasa la autenticación y controla a diferentes usuarios para acceder a diferentes recursos.

Autorización : La autorización es el proceso de autenticación del usuario para controlar el acceso del usuario a los recursos de acuerdo con los permisos del usuario. Si tiene acceso a los recursos, puede acceder normalmente, y si no tiene permiso, negará el acceso.

1.4 Modelo de datos autorizado

Cómo autorizar significa cómo controlar el acceso de los usuarios a los recursos. Primero, debe aprender el modelo de datos relacionado con la autorización.

La autorización puede entenderse simplemente como Quién realiza operaciones de Cómo en Qué (cuál), incluyendo lo siguiente:

Quién , es decir, el sujeto (Subject), el sujeto generalmente se refiere al usuario, o puede ser un programa, que necesita acceder a los recursos del sistema.

Qué , es decir, recursos (Resource), como menús del sistema, páginas, botones, métodos de código, información del producto del sistema, información de pedidos del sistema, etc. Los menús del sistema, las páginas, los botones y los métodos de código son todos recursos de función del sistema. Para los sistemas web, cada recurso de función generalmente corresponde a una URL; la información del producto del sistema y la información del pedido del sistema son todos recursos de entidad (recursos de datos) y los recursos de entidad están determinados por tipo de recurso y composición de recurso de las instancias, por ejemplo, la información del producto es el tipo de recurso, el número de producto 001 es la instancia del recurso.

Cómo , permiso/permiso (Permiso), especifica el permiso del usuario para operar el recurso, y el permiso no tiene sentido sin el recurso, como permiso de consulta de usuario, permiso de adición de usuario, permiso de llamada de un método de código, permiso de modificación del usuario numerado 001, etc., a través de los permisos se puede saber qué permisos de operación tiene el usuario para qué recursos.

La relación entre sujetos, recursos y permisos es la siguiente:
inserte la descripción de la imagen aquí

El modelo de datos relacionado con temas, recursos y permisos es el siguiente:

  • Asunto (ID de usuario, cuenta, contraseña, ...)
  • Recurso (id de recurso, nombre de recurso, dirección de acceso, ...)
  • Permisos (ID de permiso, ID de permiso, nombre de permiso, ID de recurso, ...)
  • rol (id de rol, nombre de rol, ...)
  • Relación de rol y permiso (id de rol, id de permiso, ...)
  • principal (usuario) y relaciones de roles (userid, roleid, ...)

La relación entre sujetos (usuarios), recursos y permisos es la siguiente:

inserte la descripción de la imagen aquí

Por lo general, en el desarrollo empresarial, las tablas de recursos y permisos se combinan en una sola tabla de permisos, de la siguiente manera:

  • Recurso (id de recurso, nombre de recurso, dirección de acceso, ...)
  • Permisos (ID de permiso, ID de permiso, nombre de permiso, ID de recurso, ...)

combinados:

  • Permisos (id de permiso, identificador de permiso, nombre de permiso, nombre de recurso, dirección de acceso al recurso, ...)

La relación entre los modelos de datos modificados es la siguiente:
inserte la descripción de la imagen aquí

1.5, RBAC

¿Cómo lograr la autorización? La industria generalmente implementa la autorización basada en RBAC.

1.5.1, control de acceso basado en roles

El control de acceso basado en roles de RBAC (Control de acceso basado en roles) está autorizado por rol. Por ejemplo, el rol del sujeto es el gerente general, que puede consultar los informes de operaciones de la empresa y la información sobre los salarios de los empleados. El proceso de control de acceso es el siguiente:
inserte la descripción de la imagen aquí

De acuerdo con la lógica de juicio en la figura anterior, el código de autorización se puede expresar de la siguiente manera:

if(主体.hasRole("总经理角色id")){
    
    
	查询工资
}

Si el rol requerido para consultar el salario en la figura anterior cambia a gerente general y gerente de departamento, entonces debe modificar la lógica de juicio para "juzgar si el rol del usuario es gerente general o gerente de departamento" y modificar el código de la siguiente manera:

if(主体.hasRole("总经理角色id") || 主体.hasRole("部门经理角色id")){
    
    
	查询工资
}

De acuerdo con el ejemplo anterior, se encuentra que cuando es necesario modificar el permiso del rol, es necesario modificar el código relevante de la autorización y la escalabilidad del sistema es deficiente.

1.5.2 Control de acceso basado en recursos

El control de acceso basado en recursos de RBAC (control de acceso basado en recursos) está autorizado por recurso (o permiso). Por ejemplo, los usuarios deben tener permiso para consultar el salario para consultar la información del salario de los empleados. El proceso de control de acceso es el siguiente:
inserte la descripción de la imagen aquí

De acuerdo con el juicio en la figura anterior, el código de autorización se puede expresar como:

if(主体.hasPermission("查询工资权限标识")){
    
    
	查询工资
}

Ventajas: el sistema está diseñado para definir el identificador de permiso para la consulta de salario. Incluso si los roles requeridos para la consulta de salario se cambian a gerente general y gerente de departamento, no es necesario modificar el código de autorización y el sistema tiene una gran escalabilidad.

2. Método de autenticación basado en sesión

2.1 Proceso de autenticación

El proceso basado en el método de autenticación de sesión es que después de que la autenticación del usuario sea exitosa, los datos relacionados con el usuario se generan en el servidor y se almacenan en la sesión (sesión actual), y el session_id enviado al cliente se almacena en la cookie, para que el session_id se traiga cuando el cliente lo solicite. Puede verificar si hay datos de sesión en el lado del servidor, para completar la verificación legal del usuario. Cuando el usuario cierra la sesión del sistema o la sesión caduca y se destruye, el session_id del cliente tampoco es válido.

La siguiente figura es un diagrama de flujo del método de autenticación de sesión:
inserte la descripción de la imagen aquí

El mecanismo de autenticación basado en sesiones está personalizado por la especificación de Servlet. El contenedor de Servlet se ha implementado y el usuario puede realizarlo a través del método de operación de HttpSession. La siguiente es la API de operación relacionada con HttpSession.

método significado
HttpSession getSession (creación booleana) Obtener el objeto HttpSession actual
void setAttribute (nombre de cadena, valor de objeto) Almacenar objetos en sesión
objeto getAttribute(nombre de la cadena) obtener objeto de la sesión
void removeAttribute(nombre de la cadena); eliminar el objeto en la sesión
anular invalidar () Invalidar HttpSession

2.2 Crear un proyecto

Este proyecto de caso se construye con maven y se implementa con SpringMVC y Servlet3.0.

2.2.1 Crear proyecto maven

Crear seguridad del proyecto maven-springmvc

Introduzca las siguientes dependencias de la siguiente manera, tenga en cuenta:

1. Dado que es un proyecto web, el empaquetado está configurado en war
2. Use el complemento tomcat7-maven-plugin para ejecutar el proyecto

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.test.security</groupId>
    <artifactId>security-springmvc</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>security-springmvc</finalName>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.tomcat.maven</groupId>
                    <artifactId>tomcat7-maven-plugin</artifactId>
                    <version>2.2</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </plugin>

                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <configuration>
                        <encoding>utf-8</encoding>
                        <useDefaultDelimiters>true</useDefaultDelimiters>
                        <resources>
                            <resource>
                                <directory>src/main/resources</directory>
                                <filtering>true</filtering>
                                <includes>
                                    <include>**/*</include>
                                </includes>
                            </resource>
                            <resource>
                                <directory>src/main/java</directory>
                                <includes>
                                    <include>**/*.xml</include>
                                </includes>
                            </resource>
                        </resources>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

2.2.2, Configuración del contenedor Spring

Defina ApplicationConfig.java en el paquete de configuración, que corresponde a la configuración de ContextLoaderListener en web.xml

@Configuration
@ComponentScan(basePackages = "com.test.security.springmvc",excludeFilters = {
    
    @ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class ApplicationConfig {
    
    
	//在此配置除了Controller的其它bean,比如:数据库链接池、事务管理器、业务bean等。
}

2.2.3, implementación de servletContext

Este caso adopta el método Servlet3.0 sin web.xml y define WebConfig.java en el paquete de configuración, que corresponde a la configuración de DispatcherServlet correspondiente a s.

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.test.security.springmvc",includeFilters = {
    
    @ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class WebConfig implements WebMvcConfigurer {
    
    
	//视频解析器
	@Bean
	public InternalResourceViewResolver viewResolver(){
    
    
		InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
		viewResolver.setPrefix("/WEB‐INF/views/");
		viewResolver.setSuffix(".jsp");
		return viewResolver;
	}
}

2.2.4, carga del contenedor Spring

Defina la clase de inicialización del contenedor Spring SpringApplicationInitializer en el paquete init, que implementa la interfaz WebApplicationInitializer y carga todas las clases de implementación de la interfaz WebApplicationInitializer cuando se inicia el contenedor Spring.

public class SpringApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
    
	
	@Override
	protected Class<?>[] getRootConfigClasses() {
    
    
		return new Class<?>[] {
    
     ApplicationConfig.class };//指定rootContext的配置类
	}
	
	@Override
	protected Class<?>[] getServletConfigClasses() {
    
    
		return new Class<?>[] {
    
     WebConfig.class }; //指定servletContext的配置类
	}
	
	@Override
	protected String[] getServletMappings() {
    
    
		return new String [] {
    
    "/"};
	}
}

SpringApplicationInitializer es equivalente a web.xml. Si usa el desarrollo servlet3.0, no necesita definir web.xml nuevamente. ApplicationConfig.class corresponde al application-context.xml de la siguiente configuración, y WebConfig.class corresponde a el springmvc.xml y web.xml de la siguiente referencia de contenido de configuración

<web‐app>
	<listener>
		<listener‐class>org.springframework.web.context.ContextLoaderListener</listener‐class>
	</listener>
	<context‐param>
		<param‐name>contextConfigLocation</param‐name>
		<param‐value>/WEB‐INF/application‐context.xml</param‐value>
	</context‐param>
	
	<servlet>
		<servlet‐name>springmvc</servlet‐name>
		<servlet‐class>org.springframework.web.servlet.DispatcherServlet</servlet‐class>
		<init‐param>
			<param‐name>contextConfigLocation</param‐name>
			<param‐value>/WEB‐INF/spring‐mvc.xml</param‐value>
		</init‐param>
		<load‐on‐startup>1</load‐on‐startup>
	</servlet>
	<servlet‐mapping>
		<servlet‐name>springmvc</servlet‐name>
		<url‐pattern>/</url‐pattern>
	</servlet‐mapping>
</web‐app>

2.3 Realizar la función de autenticación

2.3.1 Página de autenticación

Defina la página de autenticación login.jsp en webapp/WEB-INF/views. Este caso es solo para probar el proceso de autenticación. No se agregó ningún estilo css a la página. La página se puede implementar completando el nombre de usuario y la contraseña. Cuando se activa el inicio de sesión, la información del formulario se enviará a /login y el contenido será el siguiente:

<%@ page contentType="text/html;charset=UTF-8" pageEncoding="utf-8" %>
<html>
<head>
    <title>用户登录</title>
</head>
<body>
<form action="login" method="post">
    用户名:<input type="text" name="username"><br>&nbsp;&nbsp;&nbsp;码:
    <input type="password" name="password"><br>
    <input type="submit" value="登录">
</form>
</body>
</html>

Agregue la siguiente configuración en WebConfig para dirigir/directamente a la página login.jsp:

@Override
public void addViewControllers(ViewControllerRegistry registry) {
    
    
	registry.addViewController("/").setViewName("login");
}

Inicie el proyecto, acceda a la dirección / ruta y pruebe

inserte la descripción de la imagen aquí

2.3.2 Interfaz de autenticación

El usuario ingresa a la página de autenticación, ingresa el número de cuenta y la contraseña, hace clic para iniciar sesión y solicita /iniciar sesión para la autenticación de identidad.

(1) Defina la interfaz de autenticación, que se utiliza para verificar el nombre de usuario y la contraseña entrantes, y devuelva la información detallada del usuario si tiene éxito; de lo contrario, generará una excepción de error:

/**
* 认证服务
*/
public interface AuthenticationService {
    
    
	/**
	* 用户认证
	* @param authenticationRequest 用户认证请求
	* @return 认证成功的用户信息
	*/
	UserDto authentication(AuthenticationRequest authenticationRequest);
}

Estructura de solicitud de autenticación:

@Data
public class AuthenticationRequest {
    
    
	/**
	* 用户名
	*/
	private String username;
	/**
	* 密码
	*/
	private String password;
}

Los detalles del usuario devueltos después de una autenticación exitosa, es decir, la información del usuario actualmente conectado:

/**
* 当前登录用户信息
*/
@Data
@AllArgsConstructor
public class UserDto {
    
    
	private String id;
	private String username;
	private String password;
	private String fullname;
	private String mobile;
}

(2) La clase de implementación de autenticación, que busca la información del usuario según el nombre de usuario y verifica la contraseña, aquí se simulan dos usuarios:

@Service
public class AuthenticationServiceImpl implements  AuthenticationService{
    
    
    /**
     * 用户认证,校验用户身份信息是否合法
     *
     * @param authenticationRequest 用户认证请求,账号和密码
     * @return 认证成功的用户信息
     */
    @Override
    public UserDto authentication(AuthenticationRequest authenticationRequest) {
    
    
        //校验参数是否为空
        if(authenticationRequest == null
            || StringUtils.isEmpty(authenticationRequest.getUsername())
            || StringUtils.isEmpty(authenticationRequest.getPassword())){
    
    
            throw new RuntimeException("账号和密码为空");
        }
        //根据账号去查询数据库,这里测试程序采用模拟方法
        UserDto user = getUserDto(authenticationRequest.getUsername());
        //判断用户是否为空
        if(user == null){
    
    
            throw new RuntimeException("查询不到该用户");
        }
        //校验密码
        if(!authenticationRequest.getPassword().equals(user.getPassword())){
    
    
            throw new RuntimeException("账号或密码错误");
        }
        //认证通过,返回用户身份信息
        return user;
    }
    //根据账号查询用户信息
    private UserDto getUserDto(String userName){
    
    
        return userMap.get(userName);
    }
    
    //用户信息
    private Map<String,UserDto> userMap = new HashMap<>();
    {
    
    
        userMap.put("zhangsan",new UserDto("1010","zhangsan","123","张三","133443",authorities1));
        userMap.put("lisi",new UserDto("1011","lisi","456","李四","144553",authorities2));
    }
}

(3) Inicie sesión en el controlador y procese la solicitud de inicio de sesión. Llama al servicio de autenticación para completar la autenticación y devuelve el mensaje de aviso del resultado del inicio de sesión:

@RestController
public class LoginController {
    
    

	@Autowired
	private AuthenticationService authenticationService;
	
	/**
	* 用户登录
	* @param authenticationRequest 登录请求
	* @return
	*/
	@PostMapping(value = "/login",produces = {
    
    "text/plain;charset=UTF‐8"})
	public String login(AuthenticationRequest authenticationRequest){
    
    
		UserDetails userDetails = authenticationService.authentication(authenticationRequest);
		return userDetails.getFullname() + " 登录成功";
	}
}

(4) Prueba
Inicie el proyecto, acceso/dirección de ruta y prueba

2.4 Realizar la función de sesión

Una sesión significa que después de que un usuario inicie sesión en el sistema, el sistema recordará el estado de inicio de sesión del usuario y podrá continuar operando el sistema hasta que cierre la sesión.

El propósito de la autenticación es proteger los recursos del sistema.Cada vez que se accede a un recurso, el sistema debe saber quién está accediendo al recurso para interceptar legalmente la solicitud. Por lo tanto, después de que la autenticación sea exitosa, la información de usuario del usuario autenticado con éxito generalmente se coloca en la Sesión. En solicitudes posteriores, el sistema puede obtener el usuario actual de la Sesión e implementar el mecanismo de sesión de esta manera.

(1) Aumentar el control de la sesión
Primero defina una SESSION_USER_KEY en UserDto como la clave para almacenar la información de inicio de sesión del usuario en la sesión.

public static final String SESSION_USER_KEY = "_user";

Luego modifique el LoginController, después de que la autenticación sea exitosa, coloque la información del usuario en la sesión actual. Y agregue un método de cierre de sesión de usuario y configure la sesión para que no sea válida al cerrar la sesión.

@RestController
public class LoginController {
    
    

	@Autowired
	private AuthenticationService authenticationService;
	
	/**
	* 用户登录
	* @param authenticationRequest 登录请求
	* @param session http会话
	* @return
	*/
	@PostMapping(value = "/login",produces = "text/plain;charset=utf‐8")
	public String login(AuthenticationRequest authenticationRequest, HttpSession session){
    
    
		UserDto userDto = authenticationService.authentication(authenticationRequest);
		//用户信息存入session
		session.setAttribute(UserDto.SESSION_USER_KEY,userDto);
		return userDto.getUsername() + "登录成功";
	}
	
	@GetMapping(value = "logout",produces = "text/plain;charset=utf‐8")
	public String logout(HttpSession session){
    
    
		session.invalidate();
		return "退出成功";
	}
}

(2) Agregar recursos de prueba
Modifique LoginController y agregue el recurso de prueba 1, que obtiene el usuario de inicio de sesión actual de la sesión actual y devuelve la información de solicitud al primer plano.

/**
* 测试资源1
* @param session
* @return
*/
@GetMapping(value = "/r/r1",produces = {
    
    "text/plain;charset=UTF‐8"})
public String r1(HttpSession session){
    
    
	String fullname = null;
	Object userObj = session.getAttribute(UserDto.SESSION_USER_KEY);
	if(userObj != null){
    
    
		fullname = ((UserDto)userObj).getFullname();
	}else{
    
    
		fullname = "匿名";
	}
	return fullname + " 访问资源1";
}

2.5 Realizar la función de autorización

Ahora que hemos completado la verificación de las credenciales de identidad del usuario y el estado de inicio de sesión, y también sabemos cómo obtener la información del usuario actualmente conectado (obtenido de la sesión) A continuación, el usuario debe estar autorizado para acceder al sistema , es decir, se debe hacer lo siguiente Función:

  • Bloqueo de acceso de usuarios anónimos (usuarios no registrados): prohíbe el acceso de usuarios anónimos a ciertos recursos.
  • Intercepción de acceso de usuarios registrados: determine si se puede acceder a ciertos recursos de acuerdo con los permisos del usuario.

(1) Agregar datos de permiso
Para realizar esta función, necesitamos agregar un atributo de permiso en UserDto, que se usa para indicar el permiso que posee el usuario que inició sesión y modificar el método de construcción de UserDto al mismo tiempo.

@Data
@AllArgsConstructor
public class UserDto {
    
    
    public static final String SESSION_USER_KEY = "_user";
    //用户身份信息
    private String id;
    private String username;
    private String password;
    private String fullname;
    private String mobile;
    /**
     * 用户权限
     */
    private Set<String> authorities;
}

E inicialice los permisos para el usuario simulado en AuthenticationServiceImpl, entre los cuales Zhang San otorgó el permiso p1 y Li Si otorgó el permiso p2.

//用户信息
private Map<String,UserDto> userMap = new HashMap<>();
{
    
    
    Set<String> authorities1 = new HashSet<>();
    authorities1.add("p1");//这个p1我们人为让它和/r/r1对应
    Set<String> authorities2 = new HashSet<>();
    authorities2.add("p2");//这个p2我们人为让它和/r/r2对应
    userMap.put("zhangsan",new UserDto("1010","zhangsan","123","张三","133443",authorities1));
    userMap.put("lisi",new UserDto("1011","lisi","456","李四","144553",authorities2));
}

(2) Agregar recursos de prueba
Queremos darnos cuenta de que diferentes usuarios pueden acceder a diferentes recursos, la premisa es que debe haber múltiples recursos, así que agregue el recurso de prueba 2 en LoginController.

/**
* 测试资源2
* @param session
* @return
*/
@GetMapping(value = "/r/r2",produces = {
    
    "text/plain;charset=UTF‐8"})
public String r2(HttpSession session){
    
    
	String fullname = null;
	Object userObj = session.getAttribute(UserDto.SESSION_USER_KEY);
	if(userObj != null){
    
    
		fullname = ((UserDto)userObj).getFullname();
	}else{
    
    
		fullname = "匿名";
	}
	return fullname + " 访问资源2";
}

(3) Realizar el interceptor de autorización
Defina el interceptor SimpleAuthenticationInterceptor en el paquete interceptor para realizar la interceptación de autorización:
1. Verifique si el usuario ha iniciado sesión
2. Verifique si el usuario tiene autorización de operación

@Component
public class SimpleAuthenticationInterceptor implements HandlerInterceptor {
    
    

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        //在这个方法中校验用户请求的url是否在用户的权限范围内
        //取出用户身份信息
        Object object = request.getSession().getAttribute(UserDto.SESSION_USER_KEY);
        if(object == null){
    
    
            //没有认证,提示登录
            writeContent(response,"请登录");
        }
        UserDto userDto = (UserDto) object;
        //请求的url
        String requestURI = request.getRequestURI();
        if( userDto.getAuthorities().contains("p1") && requestURI.contains("/r/r1")){
    
    
            return true;
        }
        if( userDto.getAuthorities().contains("p2") && requestURI.contains("/r/r2")){
    
    
            return true;
        }
        writeContent(response,"没有权限,拒绝访问");
        return false;
    }

    //响应信息给客户端
    private void writeContent(HttpServletResponse response, String msg) throws IOException {
    
    
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.print(msg);
        writer.close();
        response.resetBuffer();
    }
}

Configure el interceptor en WebConfig, el recurso que coincide con /r/** es un recurso del sistema protegido y la solicitud para acceder al recurso ingresa al interceptor SimpleAuthenticationInterceptor.

@Configuration//就相当于springmvc.xml文件
@EnableWebMvc
@ComponentScan(basePackages = "com.test.security.springmvc"
        ,includeFilters = {
    
    @ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class WebConfig implements WebMvcConfigurer {
    
    

    @Autowired
    SimpleAuthenticationInterceptor simpleAuthenticationInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        registry.addInterceptor(simpleAuthenticationInterceptor).addPathPatterns("/r/**");
    }
}

(4) Prueba
Si no ha iniciado sesión, tanto /r/r1 como /r/r2 le indicarán "Inicie sesión primero".
Cuando Zhang San inicia sesión, dado que Zhang San tiene permiso p1, puede acceder a /r/r1, pero Zhang San no tiene permiso p2, y cuando accede a /r/r2, muestra "Permiso insuficiente".
Cuando Li Si inicia sesión, debido a que Li Si tiene permiso p2, puede acceder a /r/r2, pero Li Si no tiene permiso p1, y cuando accede a /r/r1, muestra "Permiso insuficiente".
Los resultados de la prueba están todos en línea con los resultados esperados.

2.6 Resumen

El método de autenticación basado en sesiones es un método de autenticación común y todavía hay muchos sistemas en uso hasta el momento. En esta sección, usamos la tecnología Spring mvc para implementarlo de manera simple, con el objetivo de que todos entiendan el significado funcional y las rutinas de implementación de la autenticación, autorización y sesión del usuario, es decir, ¿qué hacen? ¿Qué tengo que hacer?

En proyectos de producción formales, a menudo consideramos el uso de marcos de seguridad de terceros (como spring security, shiro y otros marcos de seguridad) para implementar funciones de autenticación y autorización, ya que esto puede mejorar la productividad hasta cierto punto y mejorar la estandarización del software. estos marcos a menudo La escalabilidad se considera muy completa. Pero las deficiencias también son muy obvias. Para mejorar el alcance del soporte, estos componentes generales agregarán muchas funciones que quizás no necesitemos, y la estructura será relativamente abstracta. Si no la entendemos lo suficiente, una vez ocurre un problema, será difícil de localizar.

Tres, inicio rápido de Spring Security

3.1 Introducción a Spring Security

Spring Security es un marco de seguridad que puede proporcionar soluciones de control de acceso de seguridad declarativa para sistemas de aplicaciones empresariales basados ​​en Spring. Debido a que es miembro del ecosistema Spring, se revisa y actualiza constantemente junto con todo el ecosistema Spring. Es muy simple agregar Spring Security al proyecto Spring Boot. El uso de Spring Security reduce la necesidad de escribir una gran cantidad de duplicaciones para control de seguridad del sistema empresarial El código funciona.

3.2 Crear un proyecto

3.2.1 Crear proyecto maven

Cree un proyecto maven security-spring-security
para introducir las siguientes dependencias:

<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring‐security‐web</artifactId>
	<version>5.1.4.RELEASE</version>
</dependency>
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring‐security‐config</artifactId>
	<version>5.1.4.RELEASE</version>
</dependency>

3.2.2, Configuración del contenedor Spring

@Configuration
@ComponentScan(basePackages = "com.test.security.springmvc",excludeFilters = {
    
    @ComponentScan.Filter(type = FilterType.ANNOTATION,value =
Controller.class)})
public class ApplicationConfig {
    
    
	//在此配置除了Controller的其它bean,比如:数据库链接池、事务管理器、业务bean等。
}

3.2.3, Configuración del contexto del servlet

@Configuration//就相当于springmvc.xml文件
@EnableWebMvc
@ComponentScan(basePackages = "com.test.security.springmvc"
        ,includeFilters = {
    
    @ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class WebConfig implements WebMvcConfigurer {
    
    

    //视频解析器
    @Bean
    public InternalResourceViewResolver viewResolver(){
    
    
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/view/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }  
}

3.2.4, carga del contenedor Spring

Defina la clase de inicialización del contenedor Spring SpringApplicationInitializer en el paquete init, que implementa la interfaz WebApplicationInitializer y carga todas las clases de implementación de la interfaz WebApplicationInitializer cuando se inicia el contenedor Spring.

public class SpringApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
    

    //spring容器,相当于加载 applicationContext.xml
    @Override
    protected Class<?>[] getRootConfigClasses() {
    
    
        return new Class[]{
    
    ApplicationConfig.class};
    }

    //servletContext,相当于加载springmvc.xml
    @Override
    protected Class<?>[] getServletConfigClasses() {
    
    
        return new Class[]{
    
    WebConfig.class};
    }

    //url-mapping
    @Override
    protected String[] getServletMappings() {
    
    
        return new String[]{
    
    "/"};
    }
}

3.3 Autenticación

3.3.1 Página de autenticación

springSecurity proporciona páginas de autenticación de forma predeterminada, no se requiere desarrollo adicional.

3.3.2, configuración de seguridad

Spring Security proporciona funciones de autenticación, como inicio de sesión con nombre de usuario y contraseña, cierre de sesión y administración de sesiones, que solo se pueden usar mediante configuración.

1) Defina WebSecurityConfig en el paquete de configuración. El contenido de la configuración de seguridad incluye: información de usuario, codificador de contraseña y mecanismo de interceptación de seguridad.

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    
	//配置用户信息服务
	@Bean
	public UserDetailsService userDetailsService() {
    
    
		InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
		manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
		manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
		return manager;
	}
	
	@Bean
	public PasswordEncoder passwordEncoder() {
    
    
		return NoOpPasswordEncoder.getInstance();
	}
	
	//配置安全拦截机制
	@Override
	protected void configure(HttpSecurity http) throws Exception {
    
    
		http.authorizeRequests()
		.antMatchers("/r/**").authenticated()//url匹配/r/**的资源,经过认证后才能访问。
		.anyRequest().permitAll()//其他url完全开放。
		.and()
		.formLogin().successForwardUrl("/login‐success");//支持form表单认证,认证成功后转向/login-success。
	}
}

En el método userDetailsService(), devolvemos un UserDetailsService al contenedor Spring, que Spring Security usará para obtener información del usuario. Usamos temporalmente la clase de implementación InMemoryUserDetailsManager y creamos dos usuarios, zhangsan y lisi, y establecemos contraseñas y permisos.

En configure(), establecemos reglas de intercepción de seguridad a través de HttpSecurity

2) Cargue WebSecurityConfig
y modifique el método getRootConfigClasses() de SpringApplicationInitializer para agregar WebSecurityConfig.class:

@Override
protected Class<?>[] getRootConfigClasses() {
    
    
	return new Class<?>[] {
    
     ApplicationConfig.class, WebSecurityConfig.class};
}

3.3.3, Inicialización de Spring Security

  • Si el entorno actual no usa Spring o Spring MVC, debe pasar WebSecurityConfig (clase de configuración de seguridad de Spring) a la superclase para garantizar que se obtiene la configuración y se crea el contexto de Spring.

  • Por el contrario, si spring ya se usa en el entorno actual, debemos registrar Spring Security en el springContext existente (cargamos WebSecurityConfig en rootcontext en el paso anterior), y este método no puede hacer nada.

Defina SpringSecurityApplicationInitializer en el paquete init:

public class SpringSecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
    
    
	public SpringSecurityApplicationInitializer() {
    
    
		//super(WebSecurityConfig.class);
	}
}

3.3.4 Solicitud de ruta raíz predeterminada

Agregue la ruta raíz de solicitud predeterminada para saltar a /login en WebConfig.java, esta URL proporciona seguridad de primavera:

//默认Url根路径跳转到/login,此url为spring security提供
@Override
public void addViewControllers(ViewControllerRegistry registry) {
    
    
	registry.addViewController("/").setViewName("redirect:/login");
}

3.3.5 Página de éxito de la autenticación

En la configuración de seguridad, si la autenticación es exitosa, saltará a /login-success, el código es el siguiente:

//配置安全拦截机制
@Override
protected void configure(HttpSecurity http) throws Exception {
    
    
	http.authorizeRequests()
	.antMatchers("/r/**").authenticated()
	.anyRequest().permitAll()
	.and()
	.formLogin().successForwardUrl("/login‐success");
}

Spring Security admite la autenticación de formulario y cambia a /login-success después de una autenticación exitosa.
Defina /login-success en LoginController:

@RestController
public class LoginController {
    
    

    @RequestMapping(value = "/login-success",produces = {
    
    "text/plain;charset=UTF-8"})
    public String loginSuccess(){
    
    
        return " 登录成功";
    } 
}

3.3.6 Prueba

(1) Inicie el proyecto, visite http://localhost:8080/security-spring-security/ dirección de ruta

  • La página saltará a /login de acuerdo con las reglas de configuración de addViewControllers en WebConfig, y /login es la página de inicio de sesión proporcionada por pring Security.

(2) Iniciar sesión

  • 1. Ingrese el nombre de usuario y la contraseña incorrectos
  • 2. Ingrese el nombre de usuario y la contraseña correctos y el inicio de sesión será exitoso.

(3) Salir

  • 1. Solicitar/cerrar sesión para salir
  • 2. Después de cerrar sesión y acceder a los recursos, saltará automáticamente a la página de inicio de sesión

3.4 Autorización

Para implementar la autorización, es necesario interceptar y verificar el acceso del usuario para verificar si la autoridad del usuario puede operar el recurso especificado. Spring Security proporciona un método de implementación de autorización por defecto.

Agregue /r/r1 o /r/r2 en LoginController

@RestController
public class LoginController {
    
    

    @RequestMapping(value = "/login-success",produces = {
    
    "text/plain;charset=UTF-8"})
    public String loginSuccess(){
    
    
        return " 登录成功";
    }

    /**
     * 测试资源1
     * @return
     */
    @GetMapping(value = "/r/r1",produces = {
    
    "text/plain;charset=UTF-8"})
    public String r1(){
    
    
        return " 访问资源1";
    }

    /**
     * 测试资源2
     * @return
     */
    @GetMapping(value = "/r/r2",produces = {
    
    "text/plain;charset=UTF-8"})
    public String r2(){
    
    
        return " 访问资源2";
    }
}

Configure las reglas de autorización en la clase de configuración de seguridad WebSecurityConfig.java:

//安全拦截机制(最重要)
@Override
protected void configure(HttpSecurity http) throws Exception {
    
    
   http.authorizeRequests()
          .antMatchers("/r/r1").hasAuthority("p1")
          .antMatchers("/r/r2").hasAuthority("p2")
          .antMatchers("/r/**").authenticated()//所有/r/**的请求必须认证通过
          .anyRequest().permitAll()//除了/r/**,其它的请求可以访问
          .and()
          .formLogin()//允许表单登录
          .successForwardUrl("/login-success");//自定义登录成功的页面地址

}
  • .antMatchers("/r/r1").hasAuthority("p1") significa: la url para acceder al recurso /r/r1 necesita tener la autoridad p1.

  • .antMatchers(“/r/r2”).hasAuthority(“p2”) significa: la url para acceder al recurso /r/r2 necesita tener la autoridad p2.

Prueba:
1. Inicie sesión con éxito
2. Acceda a /r/r1 y /r/r2.Si tiene permiso, puede acceder normalmente, de lo contrario, devuelva 403 (acceso denegado)

3.5 Resumen

A través de un inicio rápido, utilizamos Spring Security para implementar la autenticación y la autorización. Spring Security proporciona métodos de autenticación basados ​​en números de cuenta y contraseñas. A través de la configuración de seguridad, se pueden realizar funciones de autorización e intercepción de solicitudes. Spring Security puede hacer más que eso.

4. Explicación detallada de la aplicación Spring Security

4.1 Integrar SpringBoot

4.1.1 Introducción a Spring Boot

Spring Boot es un marco de desarrollo rápido para Spring. Basado en el diseño de Spring 4.0, el uso de Spring Boot para desarrollar puede evitar la tediosa construcción y configuración de proyectos. Al mismo tiempo, integra una gran cantidad de marcos comunes, importa rápidamente paquetes dependientes, y evita conflictos entre paquetes dependientes. Básicamente, los marcos de desarrollo comúnmente utilizados admiten el desarrollo Spring Boot, como MyBatis, Dubbo, etc., especialmente la familia Spring, como: Spring cloud, Spring mvc, Spring security, etc. El uso del desarrollo Spring Boot puede aumentar considerablemente la productividad, por lo que El uso de Spring Boot es muy alto.

A continuación, hablemos sobre cómo desarrollar aplicaciones de Spring Security a través de Spring Boot. Spring Boot proporciona spring-boot-starter-security para desarrollar aplicaciones de Spring Security.

4.1.2 Crear proyecto maven

1) Cree el proyecto maven security-spring-boot
2) Introduzca las siguientes dependencias:

<dependencies>
    <!-- 以下是>spring boot依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- 以下是>spring security依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>


    <!-- 以下是jsp依赖-->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <scope>provided</scope>
    </dependency>
    <!--jsp页面使用jstl标签 -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency>
    <!--用于编译jsp -->
    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-jasper</artifactId>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
</dependencies>

4.1.3, configuración del contenedor de resorte

El inicio del proyecto SpringBoot escaneará automáticamente todos los beans en el paquete donde se encuentra la clase de inicio y los cargará en el contenedor Spring.
1) Archivo de configuración de Spring Boot
Agregue application.properties en recursos, el contenido es el siguiente:

server.port=8080
server.servlet.context‐path=/security‐springboot
spring.application.name = security‐springboot

2) Clase de inicio Spring Boot

@SpringBootApplication
public class SecuritySpringBootApp {
    
    
	public static void main(String[] args) {
    
    
		SpringApplication.run(SecuritySpringBootApp.class, args);
	}
}

4.1.4, Configuración del contexto del servlet

Debido al mecanismo de ensamblaje automático de Spring Boot Starter, no es necesario usar @EnableWebMvc y @ComponentScan aquí, y la WebConfig es la siguiente

@Configuration//就相当于springmvc.xml文件
public class WebConfig implements WebMvcConfigurer {
    
    

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    
    
        registry.addViewController("/").setViewName("redirect:/login-view");

    }
}

El analizador de video se configura en application.properties

spring.mvc.view.prefix=/WEB‐INF/views/
spring.mvc.view.suffix=.jsp

4.1.5 Configuración de seguridad

Debido al mecanismo de ensamblaje automático de Spring Boot Starter, no es necesario usar @EnableWebSecurity aquí, y el contenido de WebSecurityConfig es el siguiente

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    

	//配置用户信息服务
	@Bean
	public UserDetailsService userDetailsService() {
    
    
		InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
		manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
		manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
		return manager;
	}
	
	@Bean
	public PasswordEncoder passwordEncoder() {
    
    
		return NoOpPasswordEncoder.getInstance();
	}
	
	//配置安全拦截机制
	@Override
	protected void configure(HttpSecurity http) throws Exception {
    
    
		http.authorizeRequests()
		.antMatchers("/r/**").authenticated()
		.anyRequest().permitAll()
		.and()
		.formLogin().successForwardUrl("/login‐success");
	}
}

4.1.6 Prueba

@RestController
public class LoginController {
    
    

    @RequestMapping(value = "/login-success",produces = {
    
    "text/plain;charset=UTF-8"})
    public String loginSuccess(){
    
    
        return " 登录成功";
    }

    /**
     * 测试资源1
     * @return
     */
    @GetMapping(value = "/r/r1",produces = {
    
    "text/plain;charset=UTF-8"})
    public String r1(){
    
    
        return " 访问资源1";
    }

    /**
     * 测试资源2
     * @return
     */
    @GetMapping(value = "/r/r2",produces = {
    
    "text/plain;charset=UTF-8"})
    public String r2(){
    
    
        return " 访问资源2";
    }
}

(1) Inicie el proyecto, visite http://localhost:8080/security-springboot/ dirección de ruta

  • La página saltará a /login de acuerdo con las reglas de configuración de addViewControllers en WebConfig, y /login es la página de inicio de sesión proporcionada por pring Security.

(2) Iniciar sesión

  • 1. Ingrese el nombre de usuario y la contraseña incorrectos
  • 2. Ingrese el nombre de usuario y la contraseña correctos y el inicio de sesión será exitoso.

(3) Salir

  • 1. Solicitar/cerrar sesión para salir
  • 2. Después de cerrar sesión y acceder a los recursos, saltará automáticamente a la página de inicio de sesión

4.2 Principio de funcionamiento

4.2.1 Descripción general de la estructura

El problema que resuelve Spring Security es el control de acceso de seguridad , y la función de control de acceso de seguridad es en realidad interceptar todas las solicitudes que ingresan al sistema y verificar si cada solicitud puede acceder a los recursos que espera. De acuerdo con el aprendizaje de los conocimientos previos, se puede realizar mediante tecnologías como Filter o AOP.La protección de los recursos web de Spring Security se realiza mediante Filter, así que comience con este Filter y profundice gradualmente en el principio de Spring Security.

Cuando se inicializa Spring Security, se creará un filtro Servlet llamado SpringSecurityFilterChain, de tipo org.springframework.security.web.FilterChainProxy, que implementa javax.servlet.Filter, por lo que las solicitudes externas pasarán a través de esta clase, como se muestra a continuación. Filtro Spring Security diagrama de estructura de cadena:

inserte la descripción de la imagen aquí

FilterChainProxy es un proxy. Lo que realmente funciona son los filtros contenidos en SecurityFilterChain en FilterChainProxy. Al mismo tiempo, estos filtros son administrados por Spring como beans. Son el núcleo de Spring Security y tienen sus propias responsabilidades, pero no manejan directamente autenticación de usuario. No procesa directamente la autorización del usuario, pero los entrega a AuthenticationManager y AccessDecisionManager para su procesamiento . La siguiente figura es un diagrama UML de las clases relacionadas con FilterChainProxy.

inserte la descripción de la imagen aquí

La realización de la función Spring Security se completa principalmente con una serie de cadenas de filtros.

inserte la descripción de la imagen aquí

A continuación se describen los principales filtros y sus funciones en la cadena de filtros:

  • SecurityContextPersistenceFilter Este filtro es la entrada y salida de todo el proceso de intercepción (es decir, el primer y último interceptor).Obtendrá el SecurityContext del SecurityContextRepository configurado al comienzo de la solicitud y luego lo establecerá en SecurityContextHolder. Una vez completada la solicitud, guarde el SecurityContext en poder de SecurityContextHolder en el SecurityContextRepository configurado y borre el SecurityContext en poder de securityContextHolder;

  • UsernamePasswordAuthenticationFilter se usa para manejar la autenticación de los envíos de formularios. El formulario debe proporcionar el nombre de usuario y la contraseña correspondientes, y también existen AuthenticationSuccessHandler y AuthenticationFailureHandler para procesar después de un inicio de sesión exitoso o fallido, que se pueden cambiar según los requisitos;

  • FilterSecurityInterceptor se usa para proteger los recursos web, usando AccessDecisionManager para autorizar el acceso al usuario actual, que se presentó en detalle anteriormente;

  • ExceptionTranslationFilter puede capturar todas las excepciones de FilterChain y manejarlas. Pero solo manejará dos tipos de excepciones: AuthenticationException y AccessDeniedException, y continuará lanzando otras excepciones.

4.2.2 Proceso de autenticación

4.2.2.1 Proceso de autenticación

inserte la descripción de la imagen aquí

Analicemos el proceso de autenticación en detalle:

  • El nombre de usuario y la contraseña enviados por el usuario se obtienen mediante el filtro UsernamePasswordAuthenticationFilter en SecurityFilterChain y se encapsulan como una autenticación de solicitud, que suele ser la clase de implementación UsernamePasswordAuthenticationToken.

  • Luego, el filtro envía la autenticación a AuthenticationManager para la autenticación.

  • Después de una autenticación exitosa, el administrador de identidades de AuthenticationManager devuelve una instancia de autenticación llena de información (incluida la información de permisos mencionada anteriormente, información de identidad, información de detalles, pero la contraseña generalmente se elimina).

  • El contenedor de contexto de seguridad SecurityContextHolder establece la Autenticación rellena con la información del paso 3 a través del método SecurityContextHolder.getContext().setAuthentication(…).

    • Se puede observar que la interfaz AuthenticationManager (administrador de autenticación) es la interfaz central relacionada con la autenticación y el punto de partida para iniciar la autenticación, su clase de implementación es ProviderManager. Spring Security admite varios métodos de autenticación, por lo que ProviderManager mantiene una lista, almacena varios métodos de autenticación y el trabajo final de autenticación lo realiza AuthenticationProvider. Sabemos que la clase de implementación de AuthenticationProvider correspondiente del formulario web es DaoAuthenticationProvider, y mantiene un UserDetailsService dentro del cual se encarga de obtener UserDetails. Finalmente, AuthenticationProvider completa UserDetails en Authentication.

La relación general entre los componentes básicos de autenticación es la siguiente:

inserte la descripción de la imagen aquí

4.2.2.2, Proveedor de autenticación

A través del proceso de autenticación anterior de Spring Security, sabemos que el administrador de autenticación (AuthenticationManager) confía al AuthenticationProvider para completar el trabajo de autenticación.

AuthenticationProvider es una interfaz definida de la siguiente manera:

public interface AuthenticationProvider {
    
    
	Authentication authenticate(Authentication authentication) throws AuthenticationException;
	boolean supports(Class<?> var1);
}

El método authenticate() define el proceso de implementación de la autenticación, su parámetro es una autenticación, que contiene el usuario y la contraseña enviados por el usuario que inició sesión. El valor devuelto también es una autenticación, que se genera después de volver a ensamblar los permisos del usuario y otra información después de que la autenticación sea exitosa.

Spring Security mantiene una lista de listas, que almacena varios métodos de autenticación, y diferentes métodos de autenticación utilizan diferentes proveedores de autenticación. Por ejemplo, hay muchos ejemplos del uso de AuthenticationProvider1 al iniciar sesión con un nombre de usuario y contraseña, el uso de AuthenticationProvider2 al iniciar sesión con un mensaje de texto, etc.

Cada AuthenticationProvider necesita implementar el método support() para indicar el método de autenticación que admite. Por ejemplo, si usamos autenticación de formulario, Spring Security generará UsernamePasswordAuthenticationToken al enviar una solicitud. Es una autenticación que encapsula la información de nombre de usuario y contraseña enviada por el usuario. . Y, en consecuencia, ¿qué proveedor de autenticación lo manejará?

Encontramos el siguiente código en la clase base AbstractUserDetailsAuthenticationProvider de DaoAuthenticationProvider:

public boolean supports(Class<?> authentication) {
    
    
	return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}

Es decir, cuando el formulario web envía el nombre de usuario y la contraseña, Spring Security es manejado por DaoAuthenticationProvider.

Finalmente, echemos un vistazo a la estructura de Autenticación (información de autenticación), que es una interfaz, y el UsernamePasswordAuthenticationToken que mencionamos anteriormente es una de sus implementaciones:

//Authentication是spring security包中的接口,直接继承自Principal类,而Principal是位于 java.security包中的。它是表示着一个抽象主体身份,任何主体都有一个名称,因此包含一个getName()方法。
public interface Authentication extends Principal, Serializable {
    
     
	//权限信息列表,默认是GrantedAuthority接口的一些实现类,通常是代表权限信息的一系列字符串。
	Collection<? extends GrantedAuthority> getAuthorities(); 
	
	//凭证信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。
	Object getCredentials(); 
	
	//细节信息,web应用中的实现接口通常为 WebAuthenticationDetails,它记录了访问者的ip地址和sessionId的值。
	Object getDetails(); 
	
	//身份信息,大部分情况下返回的是UserDetails接口的实现类,UserDetails代表用户的详细信息,那从Authentication中取出来的UserDetails就是当前登录用户信息,它也是框架中的常用接口之一。
	Object getPrincipal(); 
	
	boolean isAuthenticated();
	
	void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

4.2.2.3, Servicio de detalles de usuario

1) Conozca UserDetailsService
Ahora sabemos que DaoAuthenticationProvider maneja la lógica de autenticación de los formularios web. Después de una autenticación exitosa, se obtiene una Autenticación (implementada por UsernamePasswordAuthenticationToken), que contiene información de identidad (Principal). Esta información de identidad es un objeto, que se puede forzar en un objeto UserDetails en la mayoría de los casos.

DaoAuthenticationProvider contiene una instancia de UserDetailsService, que es responsable de extraer la información del usuario UserDetails (incluida la contraseña) de acuerdo con el nombre de usuario, y luego DaoAuthenticationProvider comparará si la contraseña del usuario extraída por UserDetailsService coincide con la contraseña enviada por el usuario como base clave para una autenticación exitosa. para que pueda pasar Un UserDetailsService personalizado se expone como un bean de primavera para definir la autenticación personalizada.

public interface UserDetailsService {
    
    
	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

Mucha gente confunde las responsabilidades de DaoAuthenticationProvider y UserDetailsService, de hecho UserDetailsService solo se encarga de cargar la información del usuario desde un lugar específico (generalmente una base de datos), nada más. El DaoAuthenticationProvider tiene una mayor responsabilidad, completa el
proceso de autenticación completo y, al mismo tiempo, completa UserDetails en Authentication.

Se ha mencionado anteriormente que UserDetails es información del usuario, echemos un vistazo a sus verdaderos colores:

public interface UserDetails extends Serializable {
    
    
	Collection<? extends GrantedAuthority> getAuthorities();
	String getPassword();
	String getUsername();
	boolean isAccountNonExpired();
	boolean isAccountNonLocked();
	boolean isCredentialsNonExpired();
	boolean isEnabled();
}

Es muy similar a la interfaz de Autenticación, por ejemplo, ambos tienen nombre de usuario y autoridades. GetCredentials() de autenticación y getPassword() de UserDetails deben tratarse de manera diferente. La primera es la credencial de contraseña enviada por el usuario, mientras que la última es la contraseña almacenada por el usuario. La autenticación es en realidad una comparación de las dos. En realidad, getAuthorities() en la autenticación se forma pasando getAuthorities() de UserDetails. ¿Recuerda el método getDetails() en la interfaz de autenticación? Entre ellos, los detalles de usuario de UserDetails se completan después de la autenticación de AuthenticationProvider.

Al implementar UserDetailsService y UserDetails, podemos completar la expansión de los métodos de adquisición de información del usuario y los campos de información del usuario.

InMemoryUserDetailsManager (autenticación de memoria) y JdbcUserDetailsManager (autenticación jdbc) proporcionados por Spring Security son las clases de implementación de UserDetailsService.La principal diferencia es que los usuarios se cargan desde la memoria o desde la base de datos.

2) prueba

Servicio personalizado de detalles de usuario

@Service
public class SpringDataUserDetailsService implements UserDetailsService {
    
    

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    
		//登录账号
		System.out.println("username="+username);
		//根据账号去数据库查询...
		//这里暂时使用静态数据
		UserDetails userDetails = User.withUsername(username).password("123").authorities("p1").build();
		return userDetails;
	}
}

Proteja la definición de UserDetailsService en la clase de configuración de seguridad

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    

    //定义用户信息服务(查询用户信息)
/*
    @Bean
    public UserDetailsService userDetailsService(){
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
        manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
        return manager;
    }
*/

}

Reinicie el proyecto, solicite la autenticación y se llamará al método loadUserByUsername de SpringDataUserDetailsService para consultar la información del usuario.

4.2.2.4, Codificador de contraseña

1) Familiarícese con PasswordEncoder
DaoAuthenticationProvider Después de que el procesador de autenticación obtiene UserDetails a través de UserDetailsService, ¿cómo se compara con la contraseña en la solicitud de autenticación?

Aquí, para adaptarse a una variedad de tipos de encriptación, Spring Security ha hecho una abstracción: DaoAuthenticationProvider realiza una comparación de contraseñas a través del método de coincidencias de la interfaz PasswordEncoder, y los detalles específicos de la comparación de contraseñas dependen de la implementación:

public interface PasswordEncoder {
    
    

	String encode(CharSequence var1);

	boolean matches(CharSequence var1, String var2);
	
	default boolean upgradeEncoding(String encodedPassword) {
    
    
		return false;
	}
}

Y Spring Security proporciona muchos codificadores de contraseña integrados, que se pueden usar de inmediato. Para usar un determinado codificador de contraseña, solo necesita hacer la siguiente declaración, de la siguiente manera:

@Bean
public PasswordEncoder passwordEncoder() {
    
    
	return NoOpPasswordEncoder.getInstance();
}

NoOpPasswordEncoder adopta el método de coincidencia de cadenas y no cifra ni compara contraseñas. El proceso de comparación de contraseñas es el siguiente:

  • 1. El usuario ingresa la contraseña (en texto plano)

  • 2. DaoAuthenticationProvider obtiene UserDetails (que almacena la contraseña correcta del usuario)

  • 3. DaoAuthenticationProvider usa PasswordEncoder para verificar la contraseña de entrada y la contraseña correcta. Si las contraseñas son consistentes, la verificación pasa, de lo contrario, la verificación falla.

La regla de verificación de NoOpPasswordEncoder compara la contraseña ingresada con la contraseña correcta en UserDetails. Si el contenido de la cadena es consistente, la verificación pasa; de lo contrario, la verificación falla.

Se recomienda utilizar BCryptPasswordEncoder, Pbkdf2PasswordEncoder, SCryptPasswordEncoder, etc. en proyectos reales. Si está interesado, puede echar un vistazo a las implementaciones específicas de estos PasswordEncoders.

2) Use BCryptPasswordEncoder
1. Configure BCryptPasswordEncoder
para definir en la clase de configuración de seguridad:

@Bean
public PasswordEncoder passwordEncoder() {
    
    
	return new BCryptPasswordEncoder();
}

La prueba encontró que la autenticación falló, lo que indica: La contraseña codificada no se parece a BCrypt.
Motivo: Dado que la contraseña original (por ejemplo: 123) se almacena en UserDetails, no está en formato BCrypt. Rastree el código en la línea 33 de DaoAuthenticationProvider para verificar el contenido en userDetails y rastree el código en la línea 38 para verificar el tipo de PasswordEncoder.

2. Pruebe BCrypt
Pruebe el método de encriptación y verificación de BCrypt a través del siguiente código
Agregue dependencias:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring‐boot‐starter‐test</artifactId>
	<scope>test</scope>
</dependency>

Escriba un método de prueba:

@RunWith(SpringRunner.class)
public class TestBCrypt {
    
    

    @Test
    public void testBCrypt(){
    
    

        //对密码进行加密
        String hashpw = BCrypt.hashpw("123", BCrypt.gensalt());
        System.out.println(hashpw);

        //校验密码
        boolean checkpw = BCrypt.checkpw("123", "$2a$10$NlBC84MVb7F95EXYTXwLneXgCca6/GipyWR5NHm8K0203bSQMLpvm");  
        System.out.println(checkpw);
    }
}

3. Modifique la clase de configuración de seguridad
para cambiar la contraseña original en UserDetails al formato BCrypt

//配置用户信息服务
@Bean
public UserDetailsService userDetailsService() {
    
    
	InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
	manager.createUser(User.withUsername("zhangsan").password("$2a$10$1b5mIkehqv5c4KRrX9bUj.A4Y2hug3IGCnMCL5i4RpQrYV12xNKye").authorities("p1").build());
	return manager;
}

Las contraseñas almacenadas en la base de datos en el proyecto real no son contraseñas originales, sino contraseñas encriptadas.

4.2.3 Proceso de autorización

4.2.3.1 Proceso de autorización

A través del inicio rápido, sabemos que Spring Security puede autorizar y proteger solicitudes web a través de http.authorizeRequests(). Spring Security utiliza el filtro estándar para establecer la intercepción de las solicitudes web y, finalmente, realiza el acceso autorizado a los recursos.

El proceso de autorización de Spring Security es el siguiente:

inserte la descripción de la imagen aquí

Analizar el proceso de autorización:

  • Al interceptar las solicitudes, el acceso del usuario autenticado a los recursos web protegidos será interceptado por la subclase de FilterSecurityInterceptor en SecurityFilterChain.

  • Obtenga la política de acceso al recurso, FilterSecurityInterceptor obtendrá la colección de permisos necesarios para acceder al recurso actual de DefaultFilterInvocationSecurityMetadataSource, una subclase de SecurityMetadataSource.

    • SecurityMetadataSource es en realidad la abstracción de la estrategia de acceso de lectura, y el contenido de lectura es en realidad la regla de acceso que configuramos. La estrategia de acceso de lectura es la siguiente:
//安全拦截机制(最重要)
@Override
protected void configure(HttpSecurity http) throws Exception {
    
    
   http.authorizeRequests()
          .antMatchers("/r/r1").hasAuthority("p1")
          .antMatchers("/r/r2").hasAuthority("p2")
          .antMatchers("/r/**").authenticated()//所有/r/**的请求必须认证通过
          .anyRequest().permitAll()//除了/r/**,其它的请求可以访问
          .and()
          .formLogin()//允许表单登录
          .successForwardUrl("/login-success");//自定义登录成功的页面地址

}
  • Finalmente, FilterSecurityInterceptor llamará a AccessDecisionManager para tomar una decisión de autorización.Si se aprueba la decisión, se permite el acceso al recurso, de lo contrario, se prohíbe el acceso.
    • La interfaz principal de AccessDecisionManager (administrador de decisiones de acceso) es la siguiente:
public interface AccessDecisionManager {
    
    
	/**
	* 通过传递的参数来决定用户是否有访问对应受保护资源的权限
	*/
	void decide(Authentication authentication , Object object, Collection<ConfigAttribute> configAttributes ) throws AccessDeniedException, InsufficientAuthenticationException;
	//略..
}

Aquí nos centramos en los parámetros de decidir:

  • autenticación: la identidad del visitante que quiere acceder al recurso
  • objeto: el recurso protegido al que se accede, la solicitud web corresponde a FilterInvocation
  • configAttributes: es la política de acceso a los recursos protegidos, obtenida a través de SecurityMetadataSource.

La interfaz de decisión se utiliza para identificar si el usuario actual tiene acceso a los recursos protegidos correspondientes.

4.2.3.2 Decisión de autorización

AccessDecisionManager utiliza la votación para determinar si se puede acceder a los recursos protegidos.

inserte la descripción de la imagen aquí

Como se puede ver en la figura anterior, se utilizará una serie de AccessDecisionVoter contenida en AccessDecisionManager para votar si la autenticación tiene derecho a acceder al objeto protegido, y AccessDecisionManager toma la decisión final en función de los resultados de la votación.

AccessDecisionVoter es una interfaz que define tres métodos y la estructura específica es la siguiente.

public interface AccessDecisionVoter<S> {
    
    
	int ACCESS_GRANTED = 1;
	int ACCESS_ABSTAIN = 0;
	int ACCESS_DENIED =1;
	
	boolean supports(ConfigAttribute var1);
	boolean supports(Class<?> var1);
	int vote(Authentication var1, S var2, Collection<ConfigAttribute> var3);
}

El resultado de retorno del método vote() será una de las tres constantes definidas en AccessDecisionVoter. ACCESS_GRANTED significa estar de acuerdo, ACCESS_DENIED significa negar, ACCESS_ABSTAIN significa abstenerse. Si AccessDecisionVoter no puede determinar si la autenticación actual tiene derecho a acceder al objeto protegido correspondiente, el valor de retorno de su método vote() debe ser ACCESS_ABSTAIN.

Spring Security tiene tres clases de implementación integradas de AccessDecisionManager basadas en votación de la siguiente manera, que son AffirmativeBased , ConsensusBased y UnanimousBased .

La lógica de AffirmativeBased es:
(1) Siempre que haya un votante por decisión de acceso que vote ACCESO_GRANTED, el usuario puede acceder;
(2) si también se aprueban todas las abstenciones;
(3) si nadie vota a favor, pero alguien vota en contra, la excepción Throws AccessDeniedException.

Spring Security usa AffirmativeBased de forma predeterminada.

La lógica de ConsensusBased es:
(1) Si hay más votos a favor que votos en contra, significa aprobado.
(2) Por el contrario, si hay más votos negativos que positivos, se lanzará una AccessDeniedException.
(3) Si los votos de aprobación son iguales a los votos negativos y no son iguales a 0, y el valor del atributo allowIfEqualGrantedDeniedDecisions es verdadero, significa que se
pasa, de lo contrario, se lanzará una excepción AccessDeniedException. El valor predeterminado del parámetro allowIfEqualGrantedDeniedDecisions es verdadero.
(4) Si todos los AccessDecisionVoters se han abstenido, dependerá del valor del parámetro allowIfAllAbstainDecisions, si el valor es verdadero, significa que pasa, de lo contrario, se lanzará una excepción AccessDeniedException. El valor predeterminado del parámetro allowIfAllAbstainDecisions es falso.

La lógica de UnanimousBased es un poco diferente de las otras dos implementaciones. Las otras dos pasarán todos los atributos de configuración del objeto protegido a AccessDecisionVoter para votar a la vez, mientras que UnanimousBased solo pasará un ConfigAttribute a AccessDecisionVoter para votar a la vez. Esto también significa que si la lógica de nuestro AccessDecisionVoter es votar a favor siempre que uno de los ConfigAttributes pasados ​​pueda coincidir, pero el resultado de la votación en UnanimousBased no es necesariamente a favor.

Específicamente, la lógica de UnanimousBased es la siguiente:
(1) Si cualquier AccessDecisionVoter se opone a un determinado ConfigAttribute de la configuración del objeto protegido, se generará AccessDeniedException.
(2) Si no hay votos negativos, pero hay votos a favor, significa aprobado.
(3) Si todos se abstuvieron, dependerá del valor del parámetro allowIfAllAbstainDecisions, verdadero pasará, falso arrojará AccessDeniedException.

Spring Security también tiene algunas clases de implementación de votantes incorporadas, como RoleVoter, AuthenticatedVoterWebExpressionVoter, etc. Puede verificar la información para aprender por sí mismo.

4.3 Autenticación personalizada

Spring Security proporciona un muy buen método de extensión de autenticación, como: almacenar información del usuario en la memoria en el inicio rápido, la información del usuario generalmente está en la base de datos en desarrollo real, Spring Security puede leer la información del usuario de la base de datos, Spring Security también admite una variedad del método de autorización.

4.3.1, página de inicio de sesión personalizada

En el inicio rápido, es posible que se pregunte de dónde proviene la página de inicio de sesión. Porque no proporcionamos ningún archivo HTML o JSP. La configuración predeterminada de Spring Security no establece explícitamente una URL de página de inicio de sesión, por lo que Spring Security generará automáticamente una URL de página de inicio de sesión de acuerdo con las funciones habilitadas y usará la URL predeterminada para procesar el contenido de envío de inicio de sesión, saltando a la URL predeterminada después de iniciar sesión , etc . Si bien las páginas de inicio de sesión generadas automáticamente son útiles para comenzar a funcionar rápidamente, la mayoría de las aplicaciones querrán definir sus propias páginas de inicio de sesión.

4.3.1.1 Página de autenticación

<%@ page contentType="text/html;charset=UTF-8" pageEncoding="utf-8" %>
<html>
<head>
    <title>用户登录</title>
</head>
<body>
<form action="login" method="post">
    用户名:<input type="text" name="username"><br>&nbsp;&nbsp;&nbsp;码:
    <input type="password" name="password"><br>
    <input type="submit" value="登录">
</form>
</body>
</html>

4.3.1.2, configurar la página de autenticación

Configure la dirección de la página de autenticación en WebConfig.java:

//默认Url根路径跳转到/login,此url为spring security提供
@Override
public void addViewControllers(ViewControllerRegistry registry) {
    
    
	registry.addViewController("/").setViewName("redirect:/login‐view");
	registry.addViewController("/login‐view").setViewName("login");
}

4.3.1.3, configuración de seguridad

Configure la información de inicio de sesión de la tabla y el capítulo en WebSecurityConfig:

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    

    //安全拦截机制(最重要)
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/r/**").authenticated()//所有/r/**的请求必须认证通过
                .anyRequest().permitAll()//除了/r/**,其它的请求可以访问
                .and()
                .formLogin()//允许表单登录
                .loginPage("/login-view")//指定我们自己的登录页,spring security以重定向方式跳转到/login-view
                .loginProcessingUrl("/login")//指定登录处理的URL,也就是用户名、密码表单提交的目的路径
                .successForwardUrl("/login-success")//指定登录成功后的跳转URL
        		.permitAll();
                
    }
}

4.3.1.4 Prueba

Dirección: localbost:8000/security-springboot/login-view
Cuando el usuario no está autenticado, el acceso a los recursos del sistema se redirigirá a la página de vista de inicio de sesión

Para evitar que ocurra CSRF (falsificación de solicitud entre sitios), Spring Security restringe la mayoría de los métodos, excepto get.
Solución 1:
Proteger el control CSRF, es decir, la seguridad de resorte ya no restringe CSRF.
Configurar WebSecurityConfig

@Override
protected void configure(HttpSecurity http) throws Exception {
    
    
	http.csrf().disable() //屏蔽CSRF控制,即spring security不再限制CSRF
	...
}

Solución 2:
agregue un token a la página login.jsp y Spring Security verificará el token. Si el token es legal, puede continuar con la solicitud.
Modificar login.jsp

<form action="login" method="post">
	<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
	...
</form>

4.3.2, Autenticación de la base de datos de conexión

En el ejemplo anterior, almacenamos la información del usuario en la memoria, y en los proyectos reales, la información del usuario se almacena en la base de datos.Esta sección realiza la lectura de la información del usuario de la base de datos. De acuerdo con la investigación previa sobre el proceso de autenticación, solo es necesario redefinir el UserDetailService para consultar la base de datos de acuerdo con la cuenta del usuario.

4.3.2.1 Crear base de datos

Crear base de datos user_db

CREATE DATABASE `user_db` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';

Crear tabla t_user

CREATE TABLE `t_user` (
	`id` bigint(20) NOT NULL COMMENT '用户id',
	`username` varchar(64) NOT NULL,
	`password` varchar(64) NOT NULL,
	`fullname` varchar(255) NOT NULL COMMENT '用户姓名',
	`mobile` varchar(11) DEFAULT NULL COMMENT '手机号',
	PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC

4.3.2.2, implementación del código

1) Definir fuente de datos
en la configuración de application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/user_db
spring.datasource.username=root
spring.datasource.password=mysql
spring.datasource.driver‐class‐name=com.mysql.jdbc.Driver

2) Agregar dependencias

<dependency>
   <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

3) Definir Dao

@Data
public class UserDto {
    
    
    private String id;
    private String username;
    private String password;
    private String fullname;
    private String mobile;
}

Defina UserDao en el paquete Dao:

@Repository
public class UserDao {
    
    
	@Autowired
	JdbcTemplate jdbcTemplate;
	
	public UserDto getUserByUsername(String username){
    
    
		String sql ="select id,username,password,fullname from t_user where username = ?";
		List<UserDto> list = jdbcTemplate.query(sql, new Object[]{
    
    username}, new BeanPropertyRowMapper<>(UserDto.class));
		if(list == null && list.size() <= 0){
    
    
			return null;
		}
		return list.get(0);
	}
}

4.3.2.3 Definir UserDetailService

Defina SpringDataUserDetailsService en el paquete de servicios:

@Service
public class SpringDataUserDetailsService implements UserDetailsService {
    
    
	@Autowired
	UserDao userDao;

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    
		//登录账号
		System.out.println("username="+username);
		//根据账号去数据库查询...
		UserDto user = userDao.getUserByUsername(username);
		if(user == null){
    
    
			return null;
		}
		//这里暂时使用静态数据
		UserDetails userDetails = User.withUsername(user.getFullname()).password(user.getPassword()).authorities("p1").build();
		return userDetails;
	}
}

4.3.2.4 Prueba

Ingrese el número de cuenta y la contraseña para solicitar la autenticación y el código de seguimiento.

4.3.2.5 Usar BCryptPasswordEncoder

De acuerdo con el método de uso de PasswordEncoder que mencionamos anteriormente, el uso de BCryptPasswordEncoder debe completar las siguientes tareas:
1. Definir BCryptPasswordEncoder en la clase de configuración de seguridad

@Bean
public PasswordEncoder passwordEncoder() {
    
    
	return new BCryptPasswordEncoder();
}

2. La contraseña en UserDetails se almacena en formato BCrypt.La
información del usuario se consulta desde la base de datos, por lo que la contraseña en la base de datos debe almacenarse en formato BCrypt.

4.4 Sesión

Después de autenticar al usuario, la información del usuario se puede guardar en la sesión para evitar la autenticación para cada operación del usuario. Spring Security proporciona gestión de sesiones.Después de pasar la autenticación, la información de identidad se coloca en el contexto SecurityContextHolder, y SecurityContext se vincula al subproceso actual para facilitar la adquisición de la identidad del usuario.

4.4.1 Obtener la identidad del usuario

Escriba LoginController, realice los recursos de prueba de /r/r1 y /r/r2, y modifique el método loginSuccess, preste atención al método getUsername, el método para que Spring Security obtenga la información de usuario de inicio de sesión actual es SecurityContextHolder.getContext(). obtener autenticación ()

@RestController
public class LoginController {
    
    
	/**
	* 用户登录成功
	* @return
	*/
	@RequestMapping(value = "/login‐success",produces = {
    
    "text/plain;charset=UTF‐8"})
	public String loginSuccess(){
    
    
		String username = getUsername()
		return username + " 登录成功";
	}

	/**
	* 获取当前登录用户名
	* @return
	*/
	private String getUsername(){
    
    
		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
		if(!authentication.isAuthenticated()){
    
    
			return null;
		}
		Object principal = authentication.getPrincipal();
		String username = null;
		if (principal instanceof org.springframework.security.core.userdetails.UserDetails) {
    
    
			username = ((org.springframework.security.core.userdetails.UserDetails)principal).getUsername();
		} else {
    
    
			username = principal.toString();
		}
		return username;
	}
	
	/**
	* 测试资源1
	* @return
	*/
	@GetMapping(value = "/r/r1",produces = {
    
    "text/plain;charset=UTF‐8"})
	public String r1(){
    
    
		String username = getUsername();
		return username + " 访问资源1";
	}

	/**
	* 测试资源2
	* @return
	*/
	@GetMapping(value = "/r/r2",produces = {
    
    "text/plain;charset=UTF‐8"})
	public String r2(){
    
    
		String username = getUsername();
		return username + " 访问资源2";
	}
}

4.4.2, control de sesión

Podemos controlar exactamente cuándo se crea una sesión y cómo Spring Security interactúa con ella a través de las siguientes opciones:

mecanismo describir
siempre Si no existe ninguna sesión, cree una
si es requerido Cree una sesión si es necesario (predeterminado) al iniciar sesión
nunca Spring Security no creará una sesión, pero si se crea una sesión en otro lugar de la aplicación, Spring Security la usará.
apátrida SpringSecurity nunca creará una sesión ni usará una sesión

Configure esta opción en la siguiente configuración:

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    

	@Override
	protected void configure(HttpSecurity http) throws Exception {
    
    
		http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
	}
}

De forma predeterminada, Spring Security creará una nueva sesión para cada usuario que inicie sesión correctamente, que es ifRequired.

Si elige nunca, le indica a Spring Security que no cree una sesión para los usuarios que inicien sesión correctamente, pero si su aplicación crea una nueva sesión en algún lugar, entonces Spring Security la usará.

Si usa stateless, significa que Spring Security no creará una sesión para los usuarios que inicien sesión correctamente y su aplicación no permitirá nuevas sesiones. E implicará que no se utilizan cookies, por lo que cada solicitud debe volver a autenticarse. Esta arquitectura sin estado funciona bien con las API REST y su mecanismo de autenticación sin estado.

Tiempo de espera de la sesión
Puede establecer el tiempo de espera de la sesión en el contenedor sevlet, de la siguiente manera para establecer el período de validez de la sesión en 3600 s;
archivo de configuración de arranque de primavera:

server.servlet.session.timeout=3600s

Después de que se agote el tiempo de la sesión, puede establecer la ruta de salto a través de Spring Security.

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    

	@Override
	protected void configure(HttpSecurity http) throws Exception {
    
    
		http.sessionManagement()
			.expiredUrl("/login‐view?error=EXPIRED_SESSION")
			.invalidSessionUrl("/login‐view?error=INVALID_SESSION");
	}
}

expired significa que la sesión ha expirado e invalidSession significa que el ID de sesión entrante no es válido.

Cookie de sesión segura
Podemos usar httpOnly y etiquetas seguras para proteger nuestra cookie de sesión:

  • httpOnly: si es verdadero, los scripts del navegador no podrán acceder a las cookies

  • seguro: si es verdadero, la cookie solo se enviará a través de conexiones HTTPS

archivo de configuración de arranque de primavera:

server.servlet.session.cookie.http‐only=true
server.servlet.session.cookie.secure=true

4.6 Salir

Spring Security implementa el cierre de sesión de forma predeterminada, acceda/cierre de sesión, como se esperaba, la función de salida Spring también lo ha hecho por nosotros.

Configure en la configuración vacía protegida (HttpSecurity http) de WebSecurityConfig:

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    

    //安全拦截机制(最重要)
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/r/**").authenticated()//所有/r/**的请求必须认证通过
                .anyRequest().permitAll()//除了/r/**,其它的请求可以访问
                .and()
                .formLogin()//允许表单登录
                .loginPage("/login-view")//指定我们自己的登录页,spring security以重定向方式跳转到/login-view
                .loginProcessingUrl("/login")//指定登录处理的URL,也就是用户名、密码表单提交的目的路径
                .successForwardUrl("/login-success")//指定登录成功后的跳转URL
                .and()
				.logout()
				.logoutUrl("/logout")
				.logoutSuccessUrl("/login‐view?logout");
                
    }
}

Cuando se activa la operación de salida, ocurrirá lo siguiente:

  • Invalidar sesión HTTP
  • Borrar SecurityContextHolder
  • Saltar a /login-view?logout

Sin embargo, de forma similar a la configuración de la función de inicio de sesión, podemos personalizar aún más la función de cierre de sesión:

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    

    //安全拦截机制(最重要)
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/r/**").authenticated()//所有/r/**的请求必须认证通过
                .anyRequest().permitAll()//除了/r/**,其它的请求可以访问
                .and()
                .formLogin()//允许表单登录
                .loginPage("/login-view")//指定我们自己的登录页,spring security以重定向方式跳转到/login-view
                .loginProcessingUrl("/login")//指定登录处理的URL,也就是用户名、密码表单提交的目的路径
                .successForwardUrl("/login-success")//指定登录成功后的跳转URL
                .and()
				.logout()//提供系统退出支持,使用 WebSecurityConfigurerAdapter 会自动被应用
				.logoutUrl("/logout")//设置触发退出操作的URL (默认是 /logout )
				.logoutSuccessUrl("/login‐view?logout")//退出之后跳转的URL。默认是 /login?logout 
				.logoutSuccessHandler(logoutSuccessHandler) //定制的 LogoutSuccessHandler ,用于实现用户退出成功时的处理。如果指定了这个选项那么logoutSuccessUrl() 的设置会被忽略。
				.addLogoutHandler(logoutHandler) //添加一个 LogoutHandler ,用于实现用户退出时的清理工作.默认 SecurityContextLogoutHandler 会被添加为最后一个 LogoutHandler 
				.invalidateHttpSession(true);//指定是否在退出时让 HttpSession 无效。 默认设置为 true。
                
    }
}

Nota: si desea que el cierre de sesión surta efecto bajo la solicitud GET, debe cerrar csrf().disable() para evitar ataques CSRF. Si CSRF está habilitado, debe usar el método de publicación para solicitar /logou

logoutHandler :
en general, las implementaciones de LogoutHandler se utilizan para realizar la limpieza necesaria, por lo que no deberían generar excepciones.

Aquí hay algunas implementaciones proporcionadas por Spring Security:

  • PersistentTokenBasedRememberMeServices Limpieza relacionada con la función RememberMe basada en el token persistente

  • TokenBasedRememberMeService Limpieza relacionada con la función RememberMe basada en token

  • Limpieza relacionada con cookies cuando CookieClearingLogoutHandler sale

  • CsrfLogoutHandler es responsable de eliminar csrfToken al salir

  • Limpieza relacionada con SecurityContext cuando SecurityContextLogoutHandler sale

La API de la cadena proporciona un atajo para llamar a la implementación LogoutHandler correspondiente, como deleteCookies().

4.7 Autorización

4.7.1 Resumen

Los métodos de autorización incluyen autorización web y autorización de método

  • La autorización web se autoriza a través de la interceptación de url
  • La autorización del método se autoriza a través de la interceptación del método.
  • Todos llamarán a accessDecisionManager para decisiones de autorización.
  • Si es autorización web, el interceptor es FilterSecurityInterceptor;
  • Si es autorización de método, el interceptor es MethodSecurityInterceptor.
  • Si la autorización web y la autorización del método se pasan al mismo tiempo, primero se ejecuta la autorización web y luego se ejecuta la autorización del método. Finalmente, si se pasa la decisión, se permite el acceso al recurso, de lo contrario, se prohíbe el acceso.

Las relaciones de clase son las siguientes:

inserte la descripción de la imagen aquí

4.7.2 Preparar el entorno

4.7.2.1, entorno de base de datos

Cree la siguiente tabla en la base de datos t_user:

Tabla de roles:

CREATE TABLE `t_role` (
	`id` varchar(32) NOT NULL,
	`role_name` varchar(255) DEFAULT NULL,
	`description` varchar(255) DEFAULT NULL,
	`create_time` datetime DEFAULT NULL,
	`update_time` datetime DEFAULT NULL,
	`status` char(1) NOT NULL,
	PRIMARY KEY (`id`),
	UNIQUE KEY `unique_role_name` (`role_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

insert into `t_role`(`id`,`role_name`,`description`,`create_time`,`update_time`,`status`) values('1','管理员',NULL,NULL,NULL,'');

Tabla de relación de roles de usuario:

CREATE TABLE `t_user_role` (
	`user_id` varchar(32) NOT NULL,
	`role_id` varchar(32) NOT NULL,
	`create_time` datetime DEFAULT NULL,
	`creator` varchar(255) DEFAULT NULL,
	PRIMARY KEY (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

insert into `t_user_role`(`user_id`,`role_id`,`create_time`,`creator`) values('1','1',NULL,NULL);

tabla de permisos:

CREATE TABLE `t_permission` (
	`id` varchar(32) NOT NULL,
	`code` varchar(32) NOT NULL COMMENT '权限标识符',
	`description` varchar(64) DEFAULT NULL COMMENT '描述',
	`url` varchar(128) DEFAULT NULL COMMENT '请求地址',
	PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

insert into `t_permission`(`id`,`code`,`description`,`url`) values ('1','p1','测试资源1','/r/r1'),('2','p3','测试资源2','/r/r2');

Tabla de relación de permisos de roles:

CREATE TABLE `t_role_permission` (
	`role_id` varchar(32) NOT NULL,
	`permission_id` varchar(32) NOT NULL,
	PRIMARY KEY (`role_id`,`permission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

insert into `t_role_permission`(`role_id`,`permission_id`) values ('1','1'),('1','2');

4.7.2.2 Modificar UserDetailService

1. Modifique la interfaz dao
Agregar en UserDao:

//根据用户id查询用户权限
public List<String> findPermissionsByUserId(String userId){
    
    
    String sql = "SELECT * FROM t_permission WHERE id IN(\n" +
            "\n" +
            "SELECT permission_id FROM t_role_permission WHERE role_id IN(\n" +
            "  SELECT role_id FROM t_user_role WHERE user_id = ? \n" +
            ")\n" +
            ")\n";

    List<PermissionDto> list = jdbcTemplate.query(sql, new Object[]{
    
    userId}, new BeanPropertyRowMapper<>(PermissionDto.class));
    List<String> permissions = new ArrayList<>();
    list.forEach(c -> permissions.add(c.getCode()));
    return permissions;
}

2. Modifique UserDetailService
para obtener permiso de lectura de la base de datos

//根据 账号查询用户信息
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    

    //将来连接数据库根据账号查询用户信息
    UserDto userDto = userDao.getUserByUsername(username);
    if(userDto == null){
    
    
        //如果用户查不到,返回null,由provider来抛出异常
        return null;
    }
    //根据用户的id查询用户的权限
    List<String> permissions = userDao.findPermissionsByUserId(userDto.getId());
    //将permissions转成数组
    String[] permissionArray = new String[permissions.size()];
    permissions.toArray(permissionArray);
    UserDetails userDetails = User.withUsername(userDto.getUsername()).password(userDto.getPassword()).authorities(permissionArray).build();
    return userDetails;
}

4.7.3, autorización web

En el ejemplo anterior, hemos completado la intercepción de autenticación y la protección de autorización simple para algunos recursos en /r/**, pero ¿qué debemos hacer si queremos implementar un control de autorización flexible? Personalice los requisitos de nuestra URL agregando varios nodos secundarios a http.authorizeRequests(), de la siguiente manera:

@Override
protected void configure(HttpSecurity http) throws Exception {
    
    
	http
		.authorizeRequests() //http.authorizeRequests() 方法有多个子节点,每个macher按照他们的声明顺序执行。
		.antMatchers("/r/r1").hasAuthority("p1")//指定"/r/r1"URL,拥有p1权限能够访问
		.antMatchers("/r/r2").hasAuthority("p2") //指定"/r/r2"URL,拥有p2权限能够访问
		.antMatchers("/r/r3").access("hasAuthority('p1') and hasAuthority('p2')")//指定了"/r/r3"URL,同时拥有p1和p2权限才能够访问
		.antMatchers("/r/**").authenticated() //指定了除了r1、r2、r3之外"/r/**"资源,同时通过身份认证就能够访问,这里使用SpEL(Spring Expression Language)表达式
		.anyRequest().permitAll() //剩余的尚未匹配的资源,不做保护。
		.and()
		.formLogin()
	// ...
}

Aviso:

El orden de las reglas es importante, primero se deben escribir reglas más específicas.
Ahora todo lo que comienza con /admin requiere un usuario autenticado con el rol ADMIN, incluso la ruta /admin/login (ya que /admin/login ya se reemplazó por /admin /** la regla coincide, por lo que se ignora la segunda regla).

.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/admin/login").permitAll()

Por lo tanto, las reglas para la página de inicio de sesión deben aparecer antes que las reglas de /admin/**.

.antMatchers("/admin/login").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")

Los métodos comunes para proteger las URL son:

  • autenticado() protege la URL y requiere que el usuario inicie sesión

  • permitAll() La URL especificada no necesita estar protegida, aplicaciones generales y archivos de recursos estáticos

  • hasRole(String role) restringe el acceso a un solo rol, el rol se incrementará en "ROLE_", por lo que "ADMIN" se comparará con "ROLE_ADMIN".

  • hasAuthority (autoridad de cadena) restringe el acceso a una sola autoridad

  • hasAnyRole(String... roles) Permite el acceso de múltiples roles.

  • hasAnyAuthority(String…autoridades) Permitir el acceso de varias autoridades.

  • acceso (atributo de cadena) Este método utiliza expresiones SpEL, por lo que se pueden crear restricciones complejas.

  • hasIpAddress(String ipaddressExpression) restringe la dirección IP o subred

4.7.4, autorización del método

Ahora que dominamos cómo usar http.authorizeRequests() para autorizar y proteger los recursos web A partir de Spring Security 2.0, admite el soporte de seguridad de los métodos de la capa de servicio. Esta sección aprende @PreAuthorize, @PostAuthorize, @Secured tres tipos de anotaciones.

Podemos usar la anotación @EnableGlobalMethodSecurity en cualquier instancia de @Configuration para habilitar la seguridad basada en anotaciones.

Lo siguiente habilitará la anotación @Secured de Spring Security.

@EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig {
    
    // ...}

Agregar una anotación a un método (en una clase o interfaz) restringe el acceso a ese método. El soporte de anotaciones nativas de Spring Security define un conjunto de atributos para este método. Estos se pasarán a AccessDecisionManager para que tome la decisión real:

public interface BankService {
    
    
	@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
	public Account readAccount(Long id);
	
	@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
	public Account[] findAccounts();
	
	@Secured("ROLE_TELLER")
	public Account post(Account account, double amount);
}

La configuración anterior indica que se puede acceder a los métodos readAccount y findAccounts de forma anónima, y ​​la capa inferior usa el votante WebExpressionVoter, que se puede rastrear desde la línea 23 de AffirmativeBased. .

El método de publicación requiere un rol de CAJERO para acceder, y la capa inferior usa el votante RoleVoter.

Use el siguiente código para habilitar el soporte para anotaciones prePost

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
    
    
	// ...
}

El código Java correspondiente es el siguiente:

public interface BankService {
    
    
	@PreAuthorize("isAnonymous()")
	public Account readAccount(Long id);
	
	@PreAuthorize("isAnonymous()")
	public Account[] findAccounts();
	
	@PreAuthorize("hasAuthority('p_transfer') and hasAuthority('p_read_account')")
	public Account post(Account account, double amount);
}

La configuración anterior indica que se puede acceder a los métodos readAccount y findAccounts de forma anónima. El método de publicación debe tener los permisos p_transfer y p_read_account para acceder. La capa inferior usa el votante WebExpressionVoter, que se puede rastrear desde la línea 23 del código AffirmativeBased.

5. Esquema de autenticación del sistema distribuido

5.1 ¿Qué es un sistema distribuido?

A medida que cambian el entorno y los requisitos del software, la arquitectura del software evoluciona de una estructura única a una arquitectura distribuida. Un sistema con una arquitectura distribuida se denomina sistema distribuido. El funcionamiento de un sistema distribuido generalmente se basa en la red. Para varios servicios, el Los servicios interactúan entre sí a través de la red para completar el procesamiento comercial del usuario. La arquitectura de microservicio popular actual es una arquitectura de sistema distribuido, como se muestra en la siguiente figura:

inserte la descripción de la imagen aquí

Las características básicas de un sistema distribuido son las siguientes:

  • 1. Distribuido: cada parte se puede implementar de forma independiente y la interacción entre los servicios se comunica a través de la red, como: servicio de pedidos, servicio de productos básicos.

  • 2. Escalabilidad: cada parte se puede implementar en modo clúster, y la expansión de hardware y software se puede realizar para algunos nodos, con cierta escalabilidad.

  • 3. Uso compartido: cada parte puede servir como un recurso compartido para proporcionar servicios externos y varias partes pueden operar recursos compartidos.

  • 4. Apertura: cada parte puede publicar la interfaz de acceso de los recursos compartidos según las necesidades y puede permitir el acceso a sistemas de terceros.

5.2 Requisitos de autenticación distribuida

Cada servicio en un sistema distribuido tendrá requisitos de autenticación y autorización. Si cada servicio implementa un conjunto de lógica de autenticación y autorización, será muy redundante. Teniendo en cuenta las características compartidas de los sistemas distribuidos, un servicio de autenticación independiente necesita manejar la autenticación y autorización del sistema. Solicitud: considerando las características de apertura del sistema distribuido, no solo proporciona autenticación para servicios internos del sistema, sino que también proporciona autenticación para sistemas de terceros. Los requisitos para la autenticación distribuida se resumen a continuación:

Autenticación y autorización unificadas

Proporcione servicios de autenticación independientes y maneje la autenticación y la autorización de manera unificada.

Independientemente de los diferentes tipos de usuarios o diferentes tipos de clientes (web, H5, APP), todos adoptan mecanismos consistentes de autenticación, autoridad y sesión para lograr una autenticación y autorización unificadas.

Para lograr la unificación, el método de autenticación debe ser escalable y admitir varios requisitos de autenticación, como: autenticación de contraseña de nombre de usuario, código de verificación de SMS, código QR, reconocimiento facial y otros métodos de autenticación, y se puede cambiar de manera muy flexible.

Autenticación de acceso a la aplicación

Debe proporcionar capacidades de expansión y apertura, proporcionar un mecanismo de acoplamiento seguro del sistema y abrir algunas API para el acceso de terceros. Se accede a las aplicaciones de un solo proveedor (servicios del sistema interno) y a las aplicaciones de terceros (aplicaciones de terceros) a través de un sistema unificado. mecanismo.

5.3 Esquema de autenticación distribuida

5.3.1 Análisis de selección de tipo

1. Método de autenticación basado en sesión
En un entorno distribuido, habrá un problema con la autenticación basada en sesión Cada servicio de aplicación necesita almacenar información de identidad de usuario en la sesión y distribuir solicitudes locales a otro servicio de aplicación a través del equilibrio de carga La información de sesión debe traerse, de lo contrario se volverá a autenticar.

inserte la descripción de la imagen aquí

En este momento, los métodos habituales son los siguientes:

  • Replicación de sesiones: sincronice sesiones entre múltiples servidores de aplicaciones para mantener las sesiones consistentes y transparentes para el mundo exterior.

  • Bloqueo de sesión: cuando un usuario accede a un servidor en el clúster, es obligatorio especificar que todas las solicitudes posteriores recaen en esta máquina.

  • Almacenamiento de sesión centralizado: almacena la sesión en la memoria caché distribuida y todas las instancias de la aplicación del servidor acceden a la sesión desde la memoria caché distribuida de manera uniforme.

En términos generales, el método de autenticación basado en la autenticación de sesión puede controlar mejor la sesión en el lado del servidor y tiene mayor seguridad. Sin embargo, el mecanismo de sesión se basa en cookies, que no se pueden usar de manera efectiva en clientes móviles complejos y diversos, y no pueden cruzar dominios.Además, con la expansión del sistema, la tolerancia a fallas de copiado, pegado y almacenamiento de sesiones debe mejorarse
. mejorado.

2. Método de autenticación basado en token
Con el método de autenticación basado en token, el servidor no necesita almacenar datos de autenticación, que es fácil de mantener y tiene una gran escalabilidad.El cliente puede almacenar el token en cualquier lugar y puede realizar el mecanismo de autenticación unificado de web y app. Sus desventajas también son obvias, debido a su información autocontenida, el token generalmente tiene una gran cantidad de datos y necesita transmitirse cada vez que se solicita, por lo que ocupa mucho ancho de banda. Además, la operación de verificación de la firma del token también traerá una carga de procesamiento adicional a la CPU.

inserte la descripción de la imagen aquí

5.3.2 Solución técnica

De acuerdo al análisis de la selección, se decide adoptar el método de autenticación basado en tokens, sus ventajas son:

  • 1. Un mecanismo adecuado para la autenticación unificada: los clientes, las aplicaciones de una parte y las aplicaciones de tres partes siguen un mecanismo de autenticación coherente.

  • 2. El método de autenticación de token es más adecuado para el acceso a aplicaciones de terceros porque es más abierto y puede usar los protocolos abiertos actualmente populares Oauth2.0, JWT, etc.

  • 3. En general, el servidor no necesita almacenar información de la sesión, lo que reduce la presión sobre el servidor.

El esquema técnico de autenticación del sistema distribuido se muestra en la siguiente figura:inserte la descripción de la imagen aquí

Descripción del proceso:

  • (1) El usuario inicia sesión a través de la parte de acceso (aplicación), y la parte de acceso adopta OAuth2.0 para autenticarse en el servicio de autenticación unificado (UAA).

  • (2) El servicio de autenticación (UAA) llama para verificar si la identidad del usuario es legal y obtener información sobre el permiso del usuario.

  • (3) El servicio de autenticación (UAA) obtiene la información de permiso de la parte de acceso y verifica si la parte de acceso es legal.

  • (4) Si tanto el usuario que inició sesión como la parte de acceso son legales, el servicio de autenticación genera un token jwt y lo devuelve a la parte de acceso, donde el jwt incluye permisos de usuario y permisos de parte de acceso.

  • (5) Posteriormente, la parte de acceso lleva el token jwt para acceder a los recursos de microservicio en la puerta de enlace API.

  • (6) La puerta de enlace API analiza el token y verifica si la autoridad de la parte de acceso puede acceder al microservicio solicitado.

  • (7) Si los permisos de la parte que accede son correctos, la puerta de enlace API adjuntará el token de texto sin formato analizado al encabezado de la solicitud original y reenviará la solicitud al microservicio.

  • (8) El microservicio recibe la solicitud y el token de texto sin formato contiene la información de identidad y permiso del usuario que inició sesión. Por lo tanto, los microservicios posteriores pueden hacer dos cosas por sí mismos:

    • 1: interceptación de la autorización del usuario (ver si el usuario actual tiene derecho a acceder al recurso)
    • 2: almacene la información del usuario en el contexto del subproceso actual (beneficioso para la lógica comercial posterior para obtener la información del usuario actual en cualquier momento)

Las responsabilidades de los servicios UAA y los componentes de la puerta de enlace API involucrados en el proceso son las siguientes:

1) Servicio de autenticación unificado (UAA),
que asume las responsabilidades de la autenticación de la parte de acceso OAuth2.0, la autenticación del usuario de inicio de sesión, la autorización y la generación de tokens, y completa las funciones reales de autenticación y autorización del usuario.

2) API Gateway
Como la única entrada al sistema, API Gateway proporciona una colección de API personalizada para la parte de acceso, y también puede tener otras responsabilidades, como autenticación, monitoreo, balanceo de carga, almacenamiento en caché, etc. El punto central del método de puerta de enlace API es que todas las partes de acceso y los consumidores acceden a los microservicios a través de una puerta de enlace unificada y manejan todas las funciones no comerciales en la capa de puerta de enlace.

Supongo que te gusta

Origin blog.csdn.net/shuai_h/article/details/130653929
Recomendado
Clasificación