Spring Security is used JWT

The rear end of the previous separate projects, log on strategy, there are many, but JWT considered to be the more popular solutions, and paper and everyone to share how Spring Security and JWT used together, thus achieving separation of the front and rear end when the sign-on solution.

1 Stateless Log

1.1 What is the status?

Stateful service, i.e., the server needs to be recorded per session client information to identify client identity, processing, typical design such as Tomcat Session request according to a user identity. For example Login: user login, we put the user's information stored in the server-side session, and give the user a cookie value, corresponding to the recorded session, then the next request, the user carries the cookie value (this step with a browser automatically) we will be able to identify the corresponding session, to find information about users. In this way now, most convenient, but there are some drawbacks, as follows:

  • Server to save large amounts of data, increase the service side pressure
  • The server saves the user state, do not support cluster deployment

1.2 What is a stateless

Micro-services cluster each service, to provide both use RESTful style interfaces. And one of the most important specifications RESTful style is: stateless services, namely:

  • The server does not store any client information requester
  • Each client must have the self-describing information request, information identifying the Client by side

Then this stateless what good is it?

  • The client requests information does not depend on the service side, you do not need to have access to multiple requests to the same server
  • Server clustering and state transparent to the client
  • The server can migrate any and stretching (can easily be clustered deployment)
  • Reducing the pressure storage server

1.3. How to achieve a stateless

Stateless login process:

  • First, the client sends the account name / password to the server for authentication
  • After authentication, the server will be encrypted and encoded user information into a token, return to the client
  • After each time the client sends a request, we need to carry authentication token
  • token server to client sends to decrypt determine the validity and obtain user logon information

1.4 JWT

1.4.1 Introduction

JWT, stands for Json Web Token, JSON is a lightweight style authorization and authentication specification, enabling stateless, distributed Web applications authorized:

JWT as a norm, and not tied to a particular language, commonly used Java implementations are open source projects jjwt on GitHub, at the following address:https://github.com/jwtk/jjwt

1.4.2 JWT data format

JWT data comprising three parts:

  • Header: the head, the head usually has two pieces of information:

    • Declared type, here is the JWT
    • Encryption algorithms, custom

We will be Base64Url coding header (decodable), to give the first portion of data.

  • Payload: payload is valid data, in the official document (RFC7519), where to seven examples of information:

    • iss (issuer): represents the issuer
    • exp (expiration time): represents the token expiration time
    • sub (subject): Theme
    • aud (audience): Audience
    • nbf (Not Before): Effective time
    • iat (Issued At): The issue of time
    • jti (JWT ID): No.

This part is also coded using Base64Url, to give a second portion of data.

  • Signature: Signature, authentication information is the whole data. The first two steps of the general data, together with the secret key encryption algorithm and services (the service key stored in the terminal, not be disclosed to the client), configured by the Header generation. Used to verify data integrity and overall reliability.

Generating a data format as shown below:

Note that, where the data through .spaced into three portions, respectively corresponding to the aforementioned three parts, in addition, where data are not wrap, wrap pictures show only for convenience only.

1.4.3 JWT interaction flow

flow chart:

Translation steps:

  1. Application or client to request authorization authorization server
  2. After obtaining the authorization, the authorization server returns the access token to the application
  3. Applications using the access token to access a protected resource (such as API)

Because the token JWT issued already contains a user's identity information, and each request would carry, such services do not need to store user information without even having to query the database, thus fully in line with the stateless RESTful specification.

1.5 JWT problems

Having said that, JWT is not perfect, some of the problems by the client to maintain login state brought here still exist, for example as follows:

  1. Renewal issue, which is one of many issues being criticized traditional cookie + session program support natural renewal, but jwt because the server does not save the user state, it is difficult to perfect solution to renew the problem, if introduced redis, although you can solve the problem, but jwt has become neither fish nor fowl.
  2. Write-off problem, because the server will not store user information, it is generally can be achieved by modifying the secret cancellation, modification after the server secret, unexpired token has been issued by the authentication will fail, thus achieving canceled, but after all, there is no tradition of cancellation Convenience.
  3. Password reset, password reset token can still access the original system, this time also need to modify the mandatory secret.
  4. Based on the second point and the third point is generally recommended that different users get different secret.

2 combat

Having said so much, then we take a look at this thing in the end how to use?

2.1 environment to build

First, we create a Spring Boot project, you need to add Spring Security relies upon creation, creation is complete, add a jjwtdependent, complete pom.xml file as follows:

<dependency>
    <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>

Then create a simple User object in the project realization UserDetails interfaces, as follows:

public class User implements UserDetails { private String username; private String password; private List<GrantedAuthority> authorities; public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } //省略getter/setter }

This is our user object, placed first backup, then create a HelloController, reads as follows:

@RestController
public class HelloController { @GetMapping("/hello") public String hello() { return "hello jwt !"; } @GetMapping("/admin") public String admin() { return "hello admin !"; } }

HelloController is very simple, there are two interfaces, designed /hellothe interface can be accessed by a user with a user role, and /adminthe interface can be accessed with a user admin role.

2.2 JWT filter configuration

JWT provided next two and associated filter configurations:

  1. Filter is a user login, user login filter check whether the user login is successful, if the login is successful, generating a token back to the client, a logon is unsuccessful login failure to the distal tips.
  2. The second filter when the other requests is transmitted, the verification token filter, if the check succeeds, proceed to let the request.

These two filters, we were to see, look at the first one:

public class JwtLoginFilter extends AbstractAuthenticationProcessingFilter { protected JwtLoginFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager) { super(new AntPathRequestMatcher(defaultFilterProcessesUrl)); setAuthenticationManager(authenticationManager); } @Override public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse resp) throws AuthenticationException, IOException, ServletException { User user = new ObjectMapper().readValue(req.getInputStream(), User.class); return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword())); } @Override protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse resp, FilterChain chain, Authentication authResult) throws IOException, ServletException { Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities(); StringBuffer as = new StringBuffer(); for (GrantedAuthority authority : authorities) { as.append(authority.getAuthority()) .append(","); } String jwt = Jwts.builder() .claim("authorities", as)//配置用户角色 .setSubject(authResult.getName()) .setExpiration(new Date(System.currentTimeMillis() + 10 * 60 * 1000)) .signWith(SignatureAlgorithm.HS512,"sang@123") .compact(); resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); out.write(new ObjectMapper().writeValueAsString(jwt)); out.flush(); out.close(); } protected void unsuccessfulAuthentication(HttpServletRequest req, HttpServletResponse resp, AuthenticationException failed) throws IOException, ServletException { resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); out.write("登录失败!"); out.flush(); out.close(); } }

About this class, I say the following:

  1. Custom JwtLoginFilter inherited from AbstractAuthenticationProcessingFilter, and the default method to achieve three of them.
  2. attemptAuthentication method, we extract from the login parameters in the user name and password, and then call AuthenticationManager.authenticate () method to automatically check.
  3. If the second step the verification is successful, it will callback to successfulAuthentication in successfulAuthentication method, and then a user role traverse ,connected, then use to generate Jwts token, in the order of the code, the process of generating a total of four arranged parameters, namely, user roles, themes, expiration date and the encryption algorithm and key, and then writes the generated token to the client.
  4. If the check fails the second step will come unsuccessfulAuthentication method, return an error message to the client can be in this method.

Let's look at a second token validation filters:

public class JwtFilter extends GenericFilterBean { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) servletRequest; String jwtToken = req.getHeader("authorization"); System.out.println(jwtToken); Claims claims = Jwts.parser().setSigningKey("sang@123").parseClaimsJws(jwtToken.replace("Bearer","")) .getBody(); String username = claims.getSubject();//获取当前登录用户名 List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList((String) claims.get("authorities")); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, authorities); SecurityContextHolder.getContext().setAuthentication(token); filterChain.doFilter(req,servletResponse); } }

On this filter, I said the following:

  1. First extracted from the authorization request header field, which is the value corresponding to the user's token.
  2. The extracted token string into a Claims object is then extracted from the Claims objects in the current user name and user roles, create a UsernamePasswordAuthenticationToken into the current Context, and then do the filtering chain request to continue execution.

So after two and JWT related filters even if configured.

2.3 Spring Security Configuration

Next we configure Spring Security, as follows:

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("admin") .password("123").roles("admin") .and() .withUser("sang") .password("456") .roles("user"); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/hello").hasRole("user") .antMatchers("/admin").hasRole("admin") .antMatchers(HttpMethod.POST, "/login").permitAll() .anyRequest().authenticated() .and() .addFilterBefore(new JwtLoginFilter("/login",authenticationManager()),UsernamePasswordAuthenticationFilter.class) .addFilterBefore(new JwtFilter(),UsernamePasswordAuthenticationFilter.class) .csrf().disable(); } }
  1. Simplicity, I did not encrypt the password, so the configuration examples of NoOpPasswordEncoder.
  2. Simplicity, not connected to the database, I configured the two users directly in memory, two users with different roles.
  3. When you configure the path rule, /hellothe interface must have a user role to access, /adminthe interface must have access to the admin role, POST requests and is /loginthe interface you can directly through to access the other interfaces must be certified.
  4. The last two custom configuration of the filter and close off csrf protection.

2.4 Test

Once this is done, even if our environment is completely built up, then start the project and then tested in POSTMAN as follows:

After a successful logon string is returned through the token base64url transcoding, a total of three sections, through a .separated, we can first .string before decoding, namely Header, as follows:

 

And then the two .characters between the decoding, namely payload:

You can see, we set the information, because the base64 encryption scheme is not only an encoding scheme, therefore, not recommended for sensitive user information into the token.

Then go access /hellointerfaces, note authentication mode value Bearer Token, Token value just acquired, as follows:

We can see, a successful visit.

Project address: https: //github.com/11500667/security

Guess you like

Origin www.cnblogs.com/xiaofengfree/p/11488932.html