takeaway
First in the public: JAVA thief ship, original is not easy, like readers can pay attention to oh! A share Java learning resources, practical experience and technical articles of the public number!
preface
Elegant, which means elegant and elegant, in ape-speak means the code is comfortable to look at and comfortable to use. There are many login authentication methods, including cookie, session, and token authentication. This article focuses on the elegant handling of token authentication based on JWT custom annotations. The elegance here is only the author’s personal taste.
First of all, we need to understand what custom annotations are. Of course, this is just a brief explanation, but it is not the focus of this article.
The basic elements of annotations
Declare the element to be used for an annotation
-
Modifiers Access modifiers must be public, not written and default to pubic;
-
Keyword The keyword is @interface.
-
Annotation Name The annotation name is the name of the user-defined annotation.
-
Annotation type element An annotation type element is the content of an annotation, which can be understood as the implementation part of a custom interface.
public @interface LoginUser {
//String name() default "hello";
}
Copy the code
Meta-annotations modify annotations
There are some meta annotations in the JDK, including @target, @Retention, @document, and @Inherited.
@Target
Table where the annotation is used, such as method, field, class. It has the following partial types:
type | describe |
---|---|
ElementType.TYPE | Applies to classes, interfaces (including annotation types), enumerations |
ElementType.FIELD | Applied to attributes (including constants in enumerations) |
ElementType.METHOD | Applied to methods |
ElementType.PARAMETER | Parameter applied to a method |
ElementType.CONSTRUCTOR | Apply to the constructor |
ElementType.LOCAL_VARIABLE | Apply to local variables |
ElementType.ANNOTATION_TYPE | Applies to annotation types |
ElementType.PACKAGE | Applied to the package |
@Retention
Indicates the life cycle of the annotation
type | describe |
---|---|
RetentionPolicy.SOURCE | Discarded at compile time and not included in the class file |
RetentionPolicy.CLASS | The JVM is discarded when loaded and is included in the class file, default |
RetentionPolicy.RUNTIME | Loaded by the JVM, contained in a class file, available at run time |
@Document
The element that indicates the annotation tag can be documented by Javadoc or a similar tool
@Inherited
An annotation that indicates that the @Inherited annotation is used, and that child classes of the labeled class also have this annotation
Now that the knowledge is in place, it’s time to implement custom annotations to solve login authentication
Introduction of depend on
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.8</version> </dependency> <! --jwt--> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.1</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.8.1</version> </dependency> </dependencies> Copy the code
Custom annotations
package com.ao.demo.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; // Define annotations to be used on parameters @Target(ElementType.PARAMETER) // Define annotations to take effect at runtime @Retention(RetentionPolicy.RUNTIME) public @interface LoginUser { } Copy the code
Implements a parser for method parameters
Here explain HandlerMethodArgumentResolver method is used for processing parameters of the parser, consists of the following two methods:
- SupportsParameter (if certain requirements are met, return true to enter resolveArgument)
- resolveArgument
package com.ao.demo.annotation.support;
import com.ao.demo.annotation.LoginUser;
import com.ao.demo.utils.UserTokenManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; @Slf4j public class LoginUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { public static final String LOGIN_TOKEN_KEY = "X-My-Token"; / * ** Determines whether the parameter type to be converted is supported* / @Override public boolean supportsParameter(MethodParameter parameter) { log.info("Come in supportsParameter, I want to determine if I support the type of argument I want to convert."); ResolveArgument = resolveArgument (); resolveArgument = resolveArgument () return parameter.getParameterType().isAssignableFrom(Integer.class) && parameter.hasParameterAnnotation(LoginUser.class); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer container,NativeWebRequest request, WebDataBinderFactory factory) throws Exception { / ** Each request checks for the presence of the HTTP header field 'x-my-token'.If so, the internal query is converted to LoginUser and then used as a request parameter.If not, as a NULL request parameter.* / String token = request.getHeader(LOGIN_TOKEN_KEY); log.info("Enter the resolveArgument and get the token" + token); Integer userId = JwtHelper.verifyTokenAndGetUserId(token); log.info("The login user ID is:"+ userId); if (userId == null) { return null; } return userId; } } Copy the code
Custom interceptors
package com.ao.demo.config;
import com.ao.demo.annotation.support.LoginUserHandlerMethodArgumentResolver;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; @Configuration public class WxWebMvcConfiguration implements WebMvcConfigurer { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { argumentResolvers.add(new LoginUserHandlerMethodArgumentResolver()); } } Copy the code
JwtHelper
package com.ao.demo.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException; import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import org.apache.commons.lang3.time.DateUtils; import java.util.*; public class JwtHelper { / / the secret key static final String SECRET = "X-My-Token"; // The signature is generated by who static final String ISSUSER = "me"; // Signature subject static final String SUBJECT = "this is my token"; // The audience signed static final String AUDIENCE = "MY-USER"; public String createToken(Integer userId){ try { Algorithm algorithm = Algorithm.HMAC256(SECRET); Map<String, Object> map = new HashMap<String, Object>(); map.put("alg"."HS256"); map.put("typ"."JWT"); String token = JWT.create() // Set the Header information .withHeader(map) // Set the Payload .withClaim("userId", userId) .withIssuer(ISSUSER) .withSubject(SUBJECT) .withAudience(AUDIENCE) // The time to generate the signature .withIssuedAt(new Date()) // When the signature expires .withExpiresAt(DateUtils.addHours(new Date(), 1)) / / sign Signature .sign(algorithm); return token; } catch (JWTCreationException exception){ exception.printStackTrace(); } return null; } public Integer verifyTokenAndGetUserId(String token) { try { Algorithm algorithm = Algorithm.HMAC256(SECRET); JWTVerifier verifier = JWT.require(algorithm) .withIssuer(ISSUSER) .build(); DecodedJWT jwt = verifier.verify(token); Map<String, Claim> claims = jwt.getClaims(); Claim claim = claims.get("userId"); return claim.asInt(); } catch (JWTVerificationException exception){ return null; } } } Copy the code
use
Add the @loginUser annotation to the interface that requires authenticated login
package com.ao.demo.web;
import com.ao.demo.annotation.LoginUser;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
@RestController public class TestController { @GetMapping("/test") public String tt(@LoginUser Integer userId){ if (userId == null) { return "Please log in first."; } return "Login successful"; } } Copy the code
test
First, use the main method to generate the token with user ID 1. The value is:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0aGlzIGlzIG15IHRva2VuIiwiYXVkIjoiTVktVVNFUiIsImlzcyI6Im1lIiwiZXhwIjoxNTk 0MTc1Nzg4LCJ1c2VySWQiOjEsImlhdCI6MTU5NDE3MjE4OH0.eBsFzFPHjtjoL3yF2LvHFkFfNH2–XkJhbXBOz5hKBo
- Login failure case
- Login Success Cases
So far, this is a more elegant way to write, just add a custom annotation to the interface that requires authentication and make the judgment. Isn’t it a bit tedious for every authenticated interface to check if the userId is null? So what’s the solution? In fact, we can use global exceptions to handle, so that each authentication interface does not have to determine. I originally thought of writing a separate article on elegant processing returns, but thought it was too short, so I merged it with this article.
Graceful processing returns results
Define an exception enumeration class
Records user exception information
@Getter
@NoArgsConstructor
@AllArgsConstructor
public enum UserExceptionEnum {
UNLOGIN(500."Please log in first!!")
/ /... Defining exception Information ; private int code; private String msg; } Copy the code
Custom exception
@Getter
public class UserException extends RuntimeException {
private UserExceptionEnum userExceptionEnum;
public UserException(UserExceptionEnum userExceptionEnum) { this.userExceptionEnum = userExceptionEnum; } } Copy the code
Encapsulate return result
@Data
public class ExceptionResult {
private int status;
private String message; private long timestamp; public ExceptionResult(ExceptionEnum em) { this.status = em.getCode(); this.message = em.getMsg(); this.timestamp = System.currentTimeMillis(); } } Copy the code
Global exception handling
@ControllerAdvice
It is more commonly used scenarios are as follows, here is not a say, you can go to understand.
- Global exception handling
- Global data binding
- Global data preprocessing
ResponseEntity
ResponseEntity Identifies the entire HTTP response: status code, header information, and corresponding body content.
@ControllerAdvice
public class CommonExceptionHandler {
@ExceptionHandler(UserException.class)
public ResponseEntity<ExceptionResult> handleException(UserException e){
return ResponseEntity.status(e.getUserExceptionEnum().getCode()).body(new ExceptionResult(e.getUserExceptionEnum()));
} /* Here you can define more than one to handle different businesses, such as user-related exceptions, merchandise order exceptions */ } Copy the code
This return result is not elegant, each business defines an exception class and exception enumeration class, and then handed over to the global exception handling, so that the code is more intuitive, business clearer point.
Login Authentication Optimization
If you don’t want to give each need login authentication interface to write a judgment, then you can to global exception handling, need only in LoginUserHandlerMethodArgumentResolver is modified, as follows:
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer container, NativeWebRequest request, WebDataBinderFactory factory) throws Exception {
/ ** Each request checks for the presence of the HTTP header field 'x-my-token'.If so, the internal query is converted to LoginUser and then used as a request parameter.If not, as a NULL request parameter.* / String token = request.getHeader(LOGIN_TOKEN_KEY); log.info("Enter the resolveArgument and get the token" + token); Integer userId = JwtHelper.verifyTokenAndGetUserId(token); log.info("The login user ID is:"+ userId); if (userId == null) { throw new UserException(UserExceptionEnum.UNLOGIN); } return userId; } Copy the code
If the userId is null, then the custom exception will be thrown
Test a wave
So the interface that requires login authentication does not have to check whether the userId is null.
Original is not easy, you can pay attention to oh! A share Java learning resources, technical articles, practical experience of the public account ~
This article is formatted using MDNICE