Descripción general
Como todos sabemos, usando JWT para la verificación del permiso. En comparación con la sesión, la ventaja de la sesión es que la sesión requiere una gran cantidad de memoria del servidor, y cuando se usan múltiples servidores, implicará problemas de sesión compartidos, lo que es más problemático al acceder a terminales móviles como teléfonos móviles.
JWT no necesita almacenarse en el servidor y no ocupa recursos del servidor (es decir, sin estado). Después de que el usuario inicia sesión, se adjunta al token al acceder a la solicitud que requiere permiso (generalmente establecido en el encabezado de solicitud HTTP). JWT no tiene el problema de compartir múltiples servidores, ni tiene problemas de acceso móvil en teléfonos móviles. Si para mejorar la seguridad, el token puede estar vinculado a la dirección IP del usuario
Proceso frontal
El usuario inició sesión a través de AJAX para obtener un token
Después de eso, al acceder requiere permiso, adjunte un token para acceder.
<! Doctype html> <html lang = "en"> <head> <meta charset = "utf-8"> <title> title </title> <script src = "http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"> </script> <script type = "aplicación/jaVaScript"; ""; ""; ""; ""; ""; " function login () {$ .post ("http: // localhost: 8080/auth/login", {username: $ ("#username"). val (), contraseña: $ ("#contraseña"). val ()}, function (data) {console.log (data); encabezado = data; data;})} function tuserpagebtn () {$.. "Get", URL: "http: // localhost: 8080/userPage", befefeSend: function (request) {request.setRequestheader ("autorización", encabezado),}; } </script> </head> <body> <scieldset> <legend> Por favor, inicie sesión </legend> <label> nombre de usuario </label> <input type = "text" id = "username"> <label> contraseña </label> <input type = "text" id = "contraseña"> <input type = "button" en ondclick = "login ()" Valor = "login"> </fieletset> id = "touserPagebtn" onClick = "touserPageBtn ()"> Access UserPage </boton> </body> </html>Proceso de backend (Spring Boot + Spring Security + JJWT)
Ideas:
Escriba una clase de entidad de usuario e inserta una pieza de datos
Clase de entidad de usuario (usuario)
@Data @entitypublic class user {@id @GeneratedValue private int id; nombre de cadena privada; contraseña de cadena privada; @ManyTomany (cascade = {cascadeType.Refresh}, fetch = fetchType.eager) @Jointable (name = "user_role", Joincolumns = {@joincolumn (name = "uid", referenciadoColumnnameName = "id")}, inversoincolumns = {@join "id")}) Lista privada <sol> roles;} Clase de entidad de rol (permiso)
@Data @entitypublic class rol {@ID @Id @GeneratedValue Private int id; nombre de cadena privada; @ManyTomany (mappedBy = "roles") Lista privada <serem> usuarios;} Insertar datos
Tabla de usuario
| identificación | nombre | contraseña |
|---|---|---|
| 1 | linyuan | 123 |
Mesa de roles
| identificación | nombre |
|---|---|
| 1 | USUARIO |
Tabla de usuarios
| uid | deshacerse |
|---|---|
| 1 | 1 |
Interfaz de capa DAO, obtiene datos a través del nombre de usuario y devuelve un objeto opcional con un valor de Java8
interfaz pública UserRepository extiende el repositorio <user, entero> {opcional <serem> findByName (name de cadena);} Escriba logindto para la transferencia de datos con la parte delantera
@DATAPublic de clase logindto implementa serializable {@notblank (mensaje = "El nombre de usuario no puede estar vacío") Nombre de usuario de cadena privada; @Notblank (mensaje = "La contraseña no puede estar vacía") Contraseña de cadena privada;} Escriba una herramienta de generación de tokens y cree con la biblioteca JJWT. Hay tres métodos en total: generar un token (devolver una cadena), analizar el token (devolver un objeto de autenticación de autenticación) y verificar el token (devolver un valor booleano)
@ComponentPublic Class JwtTokenUtils {private final logger log = loggerFactory.getLogger (jwttokenutil.class); static final static final autoridades_key = "auth"; Cadena privada SecretKey; // Clave de firma de tokenvalidez larga de Milisegundos; // Fecha de vencimiento TOMPLEZA LIGUA PRIVADOMILLISECONDSECONDSFORROMEMEMEME; // (Recuérdame) fecha de vencimiento @PostConstruct public void init () {this.secretkey = "LinyuanMima"; int Secondin1day = 1000 * 60 * 60 * 24; this.TokenValidityInmilliseConds = SecondIn1day * 2l; this.TokenvalidityInmillisEcondSforrememberMe = Secondin1day * 7l; } PRIVADO FINAL FINAL LARGO EXPRIRACIÓN TIME = 432_000_000; // Crear cadena pública token CreateTeoKen (autenticación Autenticación, boolean RememberMe) {String Authorities = Authentication.GetAuthorities (). Stream () // Obtener la cadena de permiso del usuario, como el usuario, admin .map (otorDauthority :: getAuthority) .collect (coleccionistas. Jugo (",")); long Now = (nueva fecha ()). GetTime (); // Obtener la validez de fecha de la marca de tiempo actual; // Tiempo de vencimiento de almacenamiento if (RememberMe) {Validity = New Date (ahora + this.TokenValidityInmilliseConds); } else {validez = nueva fecha (ahora + this.TokenvalidityInmillisEcondSforrEmemberMe); } return jwts.builder () // Crear token token.setsubject (autenticación.getName ()) // set User-oriented.claim (autoridades_key, autoridades) // agregue el permiso atribute.setExpiration (validity) // set de tiempo de invalidación.ssssignwith (firiveeLeRealgorithm.hs512, secretkey) // generado } // Obtener permisos de usuario Autenticación pública getAuthentication (string token) {System.out.println ("Token:"+token); Reclamos reclamos = jwts.parser () // La carga útil de token .SetSigningKey (SecretKey) .ParSeclaimsjws (token) .getBody (); Colección <? extiende GrantedAuthority> autoridades = arrays.stream (reclame.get (autorities_key) .ToString (). Split (",")) // Obtener permiso del usuario String.map (SimpleGrantedAuthority :: New) .Collect (Collectors.tolist ()); // Convertir elementos a la colección de interfaz de la interfaz de la Autoridad del usuario principal = nuevo usuario (reclame.getSubject (), "", autoridades); devolver nuevo UserNamePassWordAuthenticationToken (director ",", autoridades); } // Verifique si el token es público booleano público Boolean ValidateTokeken (token de cadena) {try {jwts.parser (). SetSigningKey (SecretKey) .parseclemlamsjws (token); // Verifique el token por retorno clave verdadero; } Catch (SignatureException e) {// Signature Exception log.info ("Signature JWT inválido."); log.trace ("Inválido JWT Signature Trace: {}", e); } Catch (malformedJWTException e) {// JWT Format Error log.info ("inválido jwt token."); log.Trace ("TRACE DE TOKEN JWT INVÁLIDO: {}", E); } catch (expiredjwtException e) {// jwt expirado log.info ("expirado jwt token"); log.trace ("traza de token jwt expirado: {}", e); } Catch (UnsupportedJWTException e) {// el jwt log.info ("sin apoyo jwt token"); log.trace ("Trace de token JWT sin apoyo: {}", e); } Catch (ilegalArgumentException e) {// La excepción de error del parámetro log.info ("JWT Token Compact of Handler no es válido"); log.Trace ("JWT Token Compact of Handler son trazas no válidas: {}", e); } return false; }} Implementar la interfaz UserDetails, que representa la clase de entidad de usuario, está envuelta en nuestro objeto de usuario, contiene permisos y otras propiedades, y puede ser utilizado por Spring Security
MyUserDetails de clase pública implementa UserDetails {usuario de usuario privado; public myuserDetails (usuario de usuario) {this.user = user; } @Override Public Collection <? extiende GrantedAuthority> getAuthorities () {list <sol> roles = user.getRoles (); Lista <ScededAuthority> autoridades = new ArrayList <> (); StringBuilder sb = new StringBuilder (); if (roles.size ()> = 1) {para (rol rol: roles) {autorities.add (new SimpleGrantedAuthority (role.getName ())); } autoridades de retorno; } autoridades de retorno; } return autorutionUtils.commaseParatedStringToAuthorityList (""); } @Override public String getPassword () {return user.getPassword (); } @Override public String getUsername () {return user.getName (); } @Override public boolean isaccountnonexpired () {return true; } @Override public boolean isaccountnonlocked () {return true; } @Override public boolean isCredentialSnonExpired () {return true; } @Override public boolean isEnable () {return true; }} Implemente la interfaz UserDetailsService, que solo tiene un método para obtener UserDetails. Podemos obtener el objeto de usuario de la base de datos, luego envuélvelo en UserDetails y devolverlo
@ServicePublic Class myUSerDetailsService Implementa UserDetailsService {@aUtoWired UserRepository UserRepository; @Override Public UserDetails LoadUserByUserName (String S) lanza usernAmenotFoundException {// Cargue el objeto de usuario desde la base de datos opcional <serem> user = userRepository.findbyName (s); // Para la depuración, si el valor existe, se emiten el nombre de usuario y la contraseña. IfPresent ((Value)-> System.out.println ("UserName:"+value.getName ()+"contraseña de usuario:"+value.getPassword ())); // Si el valor ya no es, devuelve nulo devuelve nuevo myuserDetails (user.orelse (nulo)); }} Escribe un filtro. Si el usuario lleva el token, obtendrá el token y generará un objeto de autenticación de autenticación basado en el token y lo almacenará en el SecurityContext para el control del permiso por Spring Security.
clase pública JWTAuthenticationTokenFilter extiende GenericFilterBean {private final logger log = loggerFactory.getLogger (jwtauthenticationTokenfilter.class); @AUTOWIRED Private JWTTokenutils TokenProvider; @Override public void dofilter (ServLetRequest ServLetRequest, ServLetResponse ServLetResponse, FilterChain FilterChain) lanza IOException, ServLetException {System.out.println ("JWTAuthenticationTokeKenfilter"); Pruebe {httpservletRequest httpreq = (httpservletRequest) ServLetRequest; Cadena jwt = resolvetoken (httpreq); if (stringUtils.hastext (jwt) && this.tokenprovider.validateToken (jwt)) {// verifique si el jwt es autenticación correcta autenticación = this.tokingprovider.getAuthentication (jwt); // Obtener la información de autenticación del usuario SecurityContexTholder.getContext (). SetAuthentication (autenticación); // Guardar al usuario en SecurityContext} filterChain.dofilter (ServLetRequest, ServLetResponse); } catch (expiredjwtException e) {// jwt invalid log.info ("Excepción de seguridad para el usuario {} - {}", e.getClaims (). getSubject (), e.getMessage ()); log.Trace ("Seguridad de la excepción de la excepción: {}", e); ((HttpServletResponse) ServLetResponse) .SetStatus (httpservletResponse.sc_unauthorized); }} private String res dolvetoken (httpservletRequest solicitud) {string bearerToken = request.getheader (WebSeCurityConfig.authorization_header); // obtener token del encabezado HTTP if (StringUtils.hastext (BearerToken) && BearerToken.Startswith ("Bearer")) {return BearerToken.substring (7, BearerToken.length ()); // devuelve la cadena token y elimina el portador} string jwt = request.getParameter (WebSecurityConfig.authorization_token); // Obtener token de los parámetros de solicitud if (stringUtils.hastext (jwt)) {return jwt; } return null; }} Escribe un Logincontroller. El usuario accede /auth /inicio de sesión a través del nombre de usuario y la contraseña, lo recibe a través del objeto logindto y crea un objeto de autenticación. El código es UserNamePassWordAuthenticationToken para determinar si el objeto existe. Verifique el objeto de autenticación a través del método de autenticación de AuthenticationManager. El Providermanager de la clase de implementación de AuthenticationManager verificará a través del AuthenticationProvider (procesamiento de autenticación). El Providermanager predeterminado llama a DaOauthenticationProvider para el procesamiento de autenticación. El DaoAuthenticationProvider obtendrá las colas de usuario a través de UserDetailsService (Fuente de información de autenticación), si la autenticación es exitosa, se devuelve una autención que contiene permisos, y luego se establece en SecurityContext a través de SecurityContexTholder.getContext (). SetAuthentication (), generar una token de acuerdo con la autenticación y regresar a la usuaria.
@RestControllerPublic Logincontroller {@aUtowired userRepository userRepository; @AUTOWIRED AuthenticationManager de autenticación de autenticación; @AUTOWIRED Private JwtTokenutils Jwttokenutils; @RequestMapping (value = "/auth/login", método = requestmethod.post) public string login (@Valid logindto logindto, httpServletResponse httpesponse) lanza excepción {// crea un objeto de autenticación de autenticación a través de la usernación y la contraseña, e implementan la clase como usernamepasswordotaticationTicheadeKingAwordateo WortheAwdersworteworthworthWickewortaworthWickeworta WorthAwordatication New UserNamePassWordAuthenticationToken (logindto.getUsername (), logindto.getPassword ()); // Si el objeto de autenticación no está vacío if (objects.nonnull (autenticationToken)) {userRepository.findbyName (autenticationToken.getPrincipal (). ToString ()) .orelsethrow (()-> nueva excepción ("El usuario no existe")); } try {// Verifique el objeto de autenticación a través de AuthenticationManager (implementado predeterminado como Providermanager) autenticación = autenticationManager.authenticate (autenticationToken); // Enlazar la autenticación a SecurityContext SecurityContexTholder.getContext (). SetAuthentication (autenticación); // generar token string token = jwttokenutils.createToken (autenticación, falso); // Escribe el token al encabezado http httpesponse.addheader (WebSecurityConfig.authorization_header, "Bearer"+Token); regresar "portador"+token; } Catch (BadCredentialSexception Authentication) {Throw New Exception ("Error de contraseña"); }}} Escriba la clase de configuración de seguridad, herede el WebSecurityConfigurerAdapter y anule el método Configurar
@Configuration@enablewebsecurity@enableglobalMethodSecurity (prepostenAnted = true) public class WebSecurityConfig extiende WebSecurityConfigurerAdapter {public static final String Authorization_header = "Autorización"; Public static final String Authorization_token = "access_token"; @AUTOWIREDIREDIREDEDREDEDREDEDETAILSSERVICE USERDETAILSSERVICE; @Override Protected void Configurar (AuthenticationManagerBuilder Auth) lanza la excepción {Auth // Personalizar para obtener información del usuario. UserDetailsService (UserDetailsService) // Establecer contraseña Cifryption.PassWordEnder (contraseña ()); } @Override protegido void configure (httpsecurity http) lanza la excepción {// configurar la política de acceso de solicitud http // csrf y cors .cors (). Disable () .csrf (). Disable () // No se requiere secession desde .SessionManagement (). SessionCreationPolicy (sessionCreationPolicy.stateless) .and () // Verificar http request.authorizequests () // Permitir que todos los usuarios accedan a la página de inicio y registrar.antmatchers ("/", "/auth/login"). Permitall () // Todas las demás solicitudes deben ser autenticadas. requiere permiso del usuario.antMatchers ("/UserPage"). Hasanyrole ("usuario") .and () // set logrout (). Permitall (); // Agregar filtro JWT en http .addfilterbefore (genicFilterBean (), usernamePasswordAuthenticationFilter.class); } @Bean public PasswordEncoder PasswordEncoder () {return new bcRyptPassPassWordEnder (); } @Bean Public GenericFilterBean GenericFilterBean GenericFilterBean () {return new JWTAuthenticationTokenFilter (); }} Escribe un controlador para las pruebas
@RestControllerPublic Class UserController {@PostMapping ("/login") public String Login () {return "Login"; } @Getmapping ("/") public String index () {return "Hello"; } @Getmapping ("/userPage") public String httpapi () {System.out.println (SecurityContexTholder.getContext (). GetAuthentication (). GetPrincipal ()); devolver "UserPage"; } @Getmapping ("/adminPage") public String httpsuite () {return "UserPage"; }}Descarga del código fuente del caso (descarga local)
Resumir
Lo anterior es todo el contenido de este artículo. Espero que el contenido de este artículo tenga cierto valor de referencia para el estudio o el trabajo de todos. Si tiene alguna pregunta, puede dejar un mensaje para comunicarse. Gracias por su apoyo a Wulin.com.