1. Background
When doing the general permission system, I used spring-security to control the permission system, and now I will summarize the most basic usage
2. Demo usage
2.1 Some basic concepts
Spring Security's security management has two important concepts, Authentication and Authorization
Spring Security login authentication mainly involves two important interfaces: UserDetailService and UserDetails. The UserDetailService interface mainly defines a method loadUserByUsername(String username) to complete the query of user information. username is the login name when logging in. When logging in and authenticating, you need to customize an implementation class to implement the UserDetailService interface and complete the database query. This interface returns UserDetail.
The loadUserByUsername user returns UserDetails. Our own User implements UserDetails
UserDetail is mainly used to encapsulate user information after successful authentication, that is, the user information returned by UserDetailService can use Spring's own User object, but it is best to implement the UserDetail interface and customize the user object
What is returned after successful authentication token
The token is a string generated by the server as a token for the client to request. After the first login, the server generates a token and returns this token to the client. In the future, the client only needs to bring this token to request data without having to bring the username and password again
Basic dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2.2 Spring Security authentication steps
- Customize the UserDetails class: When the entity object fields do not meet the requirements, you need to customize UserDetails, generally you need to customize UserDetails
-
Customize the UserDetailsService class, mainly used to query user information from the database.
Implement the UserDetails interface in the User class
Modify the original isAccountNonExpired, isAccountNonLocked, isCredentialsNonExpired and isEnabled properties to boolean type, and add authorities properties.
- Create a login authentication success handler, you need to return JSON data, menu permissions, etc. after successful authentication
LoginSuccessHandler implements AuthenticationSuccessHandler
Implement the onAuthenticationSuccess method
Generate token, create login result object, get output stream, save generated token to redis
- Create a login authentication failure handler, authentication failure needs to return JSON data for the front end to judge.
LoginFailureHandler implements AuthenticationFailureHandler, override onAuthenticationFailure() method
String result = JSON.toJSONString(Result.error().code(code).message(message)); outputStream.write(result.getBytes(StandardCharsets.UTF_8)); outputStream.flush(); outputStream.close();
- Create a handler when an anonymous user accesses a resource without permission. JSON needs to be prompted when an anonymous user accesses.
CustomerAccessDeniedHandler implements AccessDeniedHandler
- Create a handler when an authenticated user accesses a resource without permission. JSON needs to be prompted when there is no permission to access.
AnonymousAuthenticationHandler implements AuthenticationEntryPoint
- Configure the Spring Security configuration class to hand over the custom handlers to Spring Security.
2.3 Custom UserDetails class
3. JWT principle and usage
A typical example of trading space for time design pattern
3.1 Advantages of JWT
- Compact: Can be sent via URL, POST parameters or in HTTP header, because the data volume is small, the transmission speed is also very fast
-
Self-contained: The payload contains all the information the user needs, avoiding multiple database queries
-
Because the Token is stored on the client side in encrypted JSON form, JWT is cross-language in principle, and any web form supports it.
-
There is no need to save session information on the server side, which is especially suitable for distributed microservices.
3.2. Token composition
-
- Header
The header usually consists of two parts: the type of token (i.e. JWT) and the signature algorithm used, such as HMAC SHA256 or RSA. It will use Base64 encoding to form the first part of the JWT structure
-
- Payload
-
- Signature
The first two parts are encoded using Base64, that is, the front end can decrypt to know the information inside. The Signature needs to use the encoded header and payload and a secret key we provide, and then use the signature algorithm (HS256) specified in the header for signing. The role of the signature is to ensure that the JWT has not been tampered with
3.3 JWT encryption and decryption full process
@Test
void contextLoads() {
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND,100);
String token = JWT.create()
.withClaim("userid",12)
.withClaim("username", "xiaochen")//payload
.withExpiresAt(instance.getTime())//specify token expiration time
.sign(Algorithm.HMAC256("!Q@W#E$R"));//signature
System.out.println(token);
}
@Test
public void test(){
//Create verification object
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("!Q@W#E$R")).build();
DecodedJWT verify = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2OTYxMjg1MjMsInVzZXJpZCI6MTIsInVzZXJuYW1lIjoieGlhb2NoZW4ifQ.WhsEsCcf6c4hKr1eyhy6psQsaVIPr3Ibfqo3vM8sHKA\n");
System.out.println(verify.getClaim("userid").asInt());
System.out.println(verify.getClaim("username").asString());
System.out.println("Expiration time: "+verify.getExpiresAt());
}
3.4 Common JWT exception information
-
SignatureVerificationException: Signature inconsistent exception
-
TokenExpiredException: Token expired exception
-
AlgorithmMismatchException: Algorithm mismatch exception
-
InvalidClaimException: Invalid payload exception
3.5 JWT encapsulation tool class
public class JWTUtils {
private static final String SIGN = "!Q@W3e4r%T^Y";
/**
* Generate token header.payload.sign
*/
public static String getToken(Map<String,String> map){
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DATE,7);
JWTCreator.Builder builder = JWT.create();
map.forEach(builder::withClaim);
return builder.withExpiresAt(instance.getTime()).sign(Algorithm.HMAC256(SIGN));
}
/**
* Verify token validity, no exception means verification passed
* Get payload in token
*
*/
public static DecodedJWT verify(String token){
return JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
}
}
Comments