1. Introduction

Welcome to Spring Security Combat Dry goods series. Earlier I showed you how to write your own Jwt generator and how to return a Json Web Token after the user is authenticated. Today we’ll look at how to use Jwt access authentication in a request. The method of obtaining DEMO is at the end of this article.

2. Common Http authentication modes

To use Jwt in Http requests we need to understand the common Http authentication methods.

2.1 HTTP Basic Authentication

HTTP Basic Authentication is also called Basic Authentication. It simply uses the Base64 algorithm to encrypt the user name and password and puts the encrypted information in the request Header. In essence, the user name and password are transmitted in plain text, which is not secure. Therefore, it is best to use it in Https environment. The certification process is as follows:

401 Unauthorized: WWw-Authenticate Specifies the authentication algorithm. Realm specifies the security domain. Enter the user name and password and put the Header in the window to request again. After the authentication is successful, the server responds to the client with the 200 status code.

2.2 HTTP Digest Authentication

To compensate for the weakness of BASIC Authentication, HTTP Digest Authentication was created. It is also called digest authentication. It uses random number and MD5 algorithm to digest the user name and password. The process is similar to Http Basic Authentication, but more complex:

Step 1: Same as basic authentication, but return a response with wwW-Authenticate header field. This field contains the temporary consultation code (random number, Nonce) required for the authentication of the query response mode. The first wwW-authenticate field must contain both realm and nonce information. The client relies on sending these two values back to the server for authentication. A nonce is an arbitrary random string generated each time with the returned 401 response. The string is usually recommended as a base64-encoded hexadecimal number, but the actual content depends on the server’s implementation

Step 2: The client that receives the 401 status code returns a response containing the Authorization information of the header field required for DIGEST authentication. The header field Authorization must contain username, realm, Nonce, URI, and Response fields. Realm and Nonce are the fields in the response received from the server.

Step 3: After receiving the Authorization request of the first field, the server confirms the correctness of the authentication information. Once authenticated, a response containing the request-URI resource is returned.

In this case, the first field authorization-info will write some information about the authentication success.

2.3 SSL Client Authentication

SSL client authentication is commonly referred to as HTTPS. The security level is high, but the CA certificate fee is required. SSL authentication involves some important concepts, including the public key of a digital certificate authority, the private key and public key of the certificate, asymmetric algorithms (used with the private key and public key of the certificate), symmetric keys, and symmetric algorithms (used with the symmetric key). It’s a little bit more complicated than this.

2.4 Form Authentication

Form forms are not authenticated by the HTTP specification. Therefore, the implementation methods are also diversified. In fact, our common scanning code login and mobile verification code login belong to the category of form login. Form authentication is usually combined with the use of cookies and sessions, and is now used by many Web sites. After a user enters the user name and password on the login page, the server returns the sessionId to the browser, and the browser saves the sessionId in the Cookie of the browser. Because HTTP is stateless, browsers use cookies to store the sessionId. The next time the client sends a request with the sessionId value, the server finds that the sessionId exists and uses the sessionId as the index to obtain the authentication information of the server for authentication. Authenticated provides access to the resource.

We are working on Spring Security. JWT has the same function as sessionId, but JWT naturally carries some user information, while sessionId needs to further obtain user information.

2.5 Json Web Tokens have been Bearer Authentication

We get the Json Web Token through form authentication, so how do we use it? Jwt is normally used as a token Bearer Authentication. Bearer Authentication is a toke-based HTTP Authentication scheme in which users who request access to restricted resources from a server carry a Token as a credential that passes the test to gain access to specific resources. Originally available in RFC 6750 as part of OAuth 2.0, but can sometimes be used on its own. The way we use Bear tokens is by putting encrypted strings in the format of Bearer < Token > (Json Web tokens) in the Authorization field of the request header. Please note that there is a blank bit between the Bearer prefix and Token and that Bearer Authentication can only be used over HTTPS (SSL), similar to basic Authentication.

3. Implement interface Jwt authentication in Spring Security

Next up is Jwt authentication of the ———— interface, the highlight of our series.

3.1 Defining a Json Web Token Filter

Either of the above authentication methods can be handled using filters in Spring Security. The default base configuration for Spring Security does not provide filters for Bearer Authentication handling, but does provide filters for Basic Authentication handling:

org.springframework.security.web.authentication.www.BasicAuthenticationFilter

BasicAuthenticationFilter inherited OncePerRequestFilter. So we also imitate BasicAuthenticationFilter to realize their own JwtAuthenticationFilter. The complete code is as follows:

 package cn.felord.spring.security.filter;
 
 import cn.felord.spring.security.exception.SimpleAuthenticationEntryPoint;
 import cn.felord.spring.security.jwt.JwtTokenGenerator;
 import cn.felord.spring.security.jwt.JwtTokenPair;
 import cn.felord.spring.security.jwt.JwtTokenStorage;
 import cn.hutool.json.JSONArray;
 import cn.hutool.json.JSONObject;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.http.HttpHeaders;
 import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
 import org.springframework.security.authentication.BadCredentialsException;
 import org.springframework.security.authentication.CredentialsExpiredException;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.AuthorityUtils;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.userdetails.User;
 import org.springframework.security.web.AuthenticationEntryPoint;
 import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
 import org.springframework.util.StringUtils;
 import org.springframework.web.filter.OncePerRequestFilter;
 
 import javax.servlet.FilterChain;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.util.List;
 import java.util.Objects;
 
 /** * JWT authentication interceptor is used to intercept requests to extract JWT authentication **@author dax
  * @since2019/11/7 23:02 * /
 @Slf4j
 public class JwtAuthenticationFilter extends OncePerRequestFilter {
     private static final String AUTHENTICATION_PREFIX = "Bearer ";
     /** * The endpoint responds if authentication fails */
     private AuthenticationEntryPoint authenticationEntryPoint = new SimpleAuthenticationEntryPoint();
     private JwtTokenGenerator jwtTokenGenerator;
     private JwtTokenStorage jwtTokenStorage;
 
 
     public JwtAuthenticationFilter(JwtTokenGenerator jwtTokenGenerator, JwtTokenStorage jwtTokenStorage) {
         this.jwtTokenGenerator = jwtTokenGenerator;
         this.jwtTokenStorage = jwtTokenStorage;
     }
 
 
     @Override
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
         // If you have passed the authentication
         if(SecurityContextHolder.getContext().getAuthentication() ! =null) {
             chain.doFilter(request, response);
             return;
         }
         // Get the header, parse out the JWT, and authenticate without a token
         String header = request.getHeader(HttpHeaders.AUTHORIZATION);
         if (StringUtils.hasText(header) && header.startsWith(AUTHENTICATION_PREFIX)) {
             String jwtToken = header.replace(AUTHENTICATION_PREFIX, "");
 
 
             if (StringUtils.hasText(jwtToken)) {
                 try {
                     authenticationTokenHandle(jwtToken, request);
                 } catch(AuthenticationException e) { authenticationEntryPoint.commence(request, response, e); }}else {
                 // The security header does not have a token
                 authenticationEntryPoint.commence(request, response, new AuthenticationCredentialsNotFoundException("token is not found"));
             }
 
         }
         chain.doFilter(request, response);
     }
 
     /** * Specific authentication methods anonymous access do not carry tokens **@param jwtToken jwt token
      * @param request  request
      */
     private void authenticationTokenHandle(String jwtToken, HttpServletRequest request) throws AuthenticationException {
 
         // The valid token will be resolved according to my implementation
         JSONObject jsonObject = jwtTokenGenerator.decodeAndVerify(jwtToken);
 
         if (Objects.nonNull(jsonObject)) {
             String username = jsonObject.getStr("aud");
 
             // Get the token from the cache
             JwtTokenPair jwtTokenPair = jwtTokenStorage.get(username);
             if (Objects.isNull(jwtTokenPair)) {
                 if (log.isDebugEnabled()) {
                     log.debug("token : {} is not in cache", jwtToken);
                 }
                 // The cache fails if it does not exist
                 throw new CredentialsExpiredException("token is not in cache");
             }
             String accessToken = jwtTokenPair.getAccessToken();
 
             if (jwtToken.equals(accessToken)) {
                   // Parse permission set here
                 JSONArray jsonArray = jsonObject.getJSONArray("roles");
 
                 String roles = jsonArray.toString();
 
                 List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(roles);
                 User user = new User(username, "[PROTECTED]", authorities);
                 // Build a user authentication token
                 UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user, null, authorities);
                 usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                 // Put it in the security context
                 SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
             } else {
                 // Token does not match
                 if (log.isDebugEnabled()){
                     log.debug("token : {} is not in matched", jwtToken);
                 }
 
                 throw new BadCredentialsException("token is not matched"); }}else {
             if (log.isDebugEnabled()) {
                 log.debug("token : {} is invalid", jwtToken);
             }
             throw new BadCredentialsException("token is invalid"); }}}Copy the code

Look at the code comments, where the logic is tailored to your business. Anonymous access must not be Token!

3.2 configuration JwtAuthenticationFilter

First, inject the JwtAuthenticationFilter into the Spring IoC container, Then must put JwtAuthenticationFilter order at UsernamePasswordAuthenticationFilter before:

        @Override
         protected void configure(HttpSecurity http) throws Exception {
             http.csrf().disable()
                     .cors()
                     .and()
                     // The session generation policy uses stateless policy
                     .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                     .and()
                     .exceptionHandling().accessDeniedHandler(new SimpleAccessDeniedHandler()).authenticationEntryPoint(new SimpleAuthenticationEntryPoint())
                     .and()
                     .authorizeRequests().anyRequest().authenticated()
                     .and()
                     .addFilterBefore(preLoginFilter, UsernamePasswordAuthenticationFilter.class)
                     / / JWT must be configured before the UsernamePasswordAuthenticationFilter
                     .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                     // An error message is returned if the JWT token fails.formLogin().loginProcessingUrl(LOGIN_PROCESSING_URL).successHandler(authenticationSuccessHandler).failureHandler(authen ticationFailureHandler) .and().logout().addLogoutHandler(new CustomLogoutHandler()).logoutSuccessHandler(new CustomLogoutSuccessHandler());
 
         }
Copy the code

4. Use Jwt for request validation

Write a limited interface, we here at http://localhost:8080/foo/test. Direct requests will be 401. We obtain the Token in the following way:

Then use Jwt in Postman:

Eventually, authentication succeeds and the resource is accessed.

5. Refresh Jwt tokens

Json Web tokens come in pairs. Json Web tokens come in pairs. AccessToken is used for interface requests, and refreshToken is used to refresh accessToken. We can also define a Filter by referring to the JwtAuthenticationFilter above. Except this time the request carries a refreshToken, and we intercept the URI in the filter to match the refresh endpoint we defined. Verify the Token and return the Token pair as if the login is successful. No more code demonstrations here.

6. Summary

This is a series of original articles, there are always not carefully read the students caught confused quite complain. Food needs to be eaten mouthful by mouthful, there is no ready to eat, is so come over, what’s the hurry. Originality is not easy, attention is power. Each article in the Spring Security Practical Goods series has different knowledge points, and they are all related. Look back if you don’t understand. Spring Security is not hard to learn. It’s all about getting your ideas right. This DEMO can be obtained by following the public account: Felordcn reply ss08.

Follow our public id: Felordcn for more information

Personal blog: https://felord.cn