Spring Security integration JWT, to achieve single sign-on, So Easy ~!

Front finishing off a separate front and rear end SpringBoot Security, log out and so return json data, that is, with Spring Security, based SpringBoot2.1.4 RELEASE separate front and rear end of the case, to achieve a landing logout feature bright spot is that with the JSON receiving a return parameter form. This is the background for a single service, SecurityContextHolder login information is stored in the cache. If two or more applications of it, then how to do? Session can not be used, Cookie natural nor can, after all, is a pair of Talia.

Ever thought about using OAuth2 to solve this problem, but OAuth2 too complicated, first of all understand the concept will take some time, but the inside of the authorization server resource server, client, and so people innocently tell, there are four kinds of authorization mode, repeatedly measure, which in the end use, the concept has not yet started to pull clear of the tangle which one to use. Starting from the concept is not a good idea, nor is it a relaxing idea. OAuth2 understanding of the process, the thought of his project is the separation of the front and rear end, can not be separated JSON, Met JWT. What the hell is JWT, Hey, is it your struggling to find it? !

So, what is it JWT? Ruan Yifeng experts look at the blog, know, JWT is a JSON Web Token short, it is to solve cross-domain problems. It seems, is looking for its simple, but it is also workable.
Continue to get to the bottom, JWT and SpringSecurity in the end is how to combine it. The following code before the code first explain, in this example, involves two items, one item is logged in item A, another project is the based access token B. Where B projects are not logged in, it will not involve logged in, as long as you can access the Token, the Token will not visit a failure.

A project is a project log, a background is accessible only by logging service. B project is a service, as long as the user is logged A project, you can visit.

Here Insert Picture Description

Design -1

A configuration program, as follows
a first step, A project file is introduced POM.xml

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

The second step, A project SecurityConfig configuration

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;

import com.example.demo.filter.JWTAuthenticationFilter;
import com.example.demo.filter.JWTLoginFilter;

/**

*/
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private UserDetailsService myCustomUserService;

@Autowired
private MyPasswordEncoder myPasswordEncoder;

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        //关闭跨站请求防护
        .cors().and().csrf().disable()
        //允许不登陆就可以访问的方法,多个用逗号分隔
        .authorizeRequests().antMatchers("/test").permitAll()
        //其他的需要授权后访问
        .anyRequest().authenticated()
        
        .and()         
        //增加登录拦截
        .addFilter(new JWTLoginFilter(authenticationManager()))     
        //增加是否登陸过滤
        .addFilter(new JWTAuthenticationFilter(authenticationManager()))
        // 前后端分离是无状态的,所以暫時不用session,將登陆信息保存在token中。
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    

}

@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
    //覆盖UserDetailsService类
    auth.userDetailsService(myCustomUserService)
    //覆盖默认的密码验证类
    .passwordEncoder(myPasswordEncoder);
}

}

The third step is the implementation class of the custom configuration file

MyPasswordEncoder PasswordEncoder class implements the default interface that can be personalized for password encryption and password contrast

import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

/**

  • Since the definition of password encryption methods to achieve PasswordEncoder Interface

  • @author Cheng on life

  • @date 2019年5月26日
    */
    @Component
    public class MyPasswordEncoder implements PasswordEncoder {

    @Override
    public String encode (CharSequence CharSequence) {
    // encryption method can be modified according to their needs
    return charSequence.toString ();
    }

    @Override
    public boolean matches(CharSequence charSequence, String s) {
    return encode(charSequence).equals(s);
    }
    }

MyCustomUserService achieve a framework default UserDetailsService, the user can get from the database based on username, see if the user exists

/**

  • Log in special classes, user login, query the database through here

  • Custom class that implements the interface UserDetailsService, called when the user logs in first class

  • @author Cheng on life

  • @date 2019年5月26日
    */
    @Component
    public class MyCustomUserService implements UserDetailsService {

    /**

    • When the login authentication, access rights information for all users by username
    • And return UserDetails into the global cache SecurityContextHolder spring in order to authorize for use
      * /
      @Override
      public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException {
      // Here you can call your own database, query on the username to see in the database whether there
      MyUserDetails myUserDetail new new MyUserDetails = ();
      myUserDetail.setUsername (username);
      myUserDetail.setPassword ( "123456");
      return myUserDetail;
      }
      }

MyUserDetails UserDetails implements the interface frame may be added according to need their required attributes in the class

import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

/**

  • UserDetails implements the interface, leaving only the necessary attributes, but they need to add properties

  • @author Cheng on life

  • @date 2019年5月26日
    */
    public class MyUserDetails implements UserDetails {

    private static final long serialVersionUID = 1L;

    // Login
    Private String username;
    // login password
    private String password;

    private Collection<? extends GrantedAuthority> authorities;

    public void setUsername(String username) {
    this.username = username;
    }

    public void setPassword(String password) {
    this.password = password;
    }

    public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
    this.authorities = authorities;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
    return this.authorities;
    }

    @Override
    public String getPassword() {
    return this.password;
    }

    @Override
    public String getUsername() {
    return this.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;
    }
    }

JWTLoginFilter achieve a framework that comes UsernamePasswordAuthenticationFilter interface to intercept treatment do so after a successful login, the head set token is returned; regardless of login success or failure, has returned JSON data

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.example.demo.entity.User;
import com.example.demo.security.MyUserDetails;
import com.fasterxml.jackson.databind.ObjectMapper;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

/**

  • After verifying the correct user name and password, generate a token, on the header, the return to the client

*/
public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter {

private AuthenticationManager authenticationManager;     

public JWTLoginFilter(AuthenticationManager authenticationManager) { 
    
    this.authenticationManager = authenticationManager;    
    
} 

/**
 * 接收并解析用户凭证,出現错误时,返回json数据前端
 */
@Override    
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res){        
    try {            
        User user =new ObjectMapper().readValue(req.getInputStream(), User.class);             
        return authenticationManager.authenticate(                    
                new UsernamePasswordAuthenticationToken(                            
                        user.getUsername(),                            
                        user.getPassword(),                            
                        new ArrayList<>())            
                );        
        } catch (Exception e) {
            try {
                //未登錄出現賬號或密碼錯誤時,使用json進行提示
                res.setContentType("application/json;charset=utf-8");
                res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                PrintWriter out = res.getWriter();                  
                Map<String,Object> map = new HashMap<String,Object>();
                map.put("code",HttpServletResponse.SC_UNAUTHORIZED);
                map.put("message","账号或密码错误!");
                out.write(new ObjectMapper().writeValueAsString(map));
                out.flush();
                out.close();
            } catch (Exception e1) {
                e1.printStackTrace();
            }
            throw new RuntimeException(e);        
        }    
}

/**
 * 用户登录成功后,生成token,并且返回json数据给前端
 */
@Override    
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res,FilterChain chain, Authentication auth){         
    
    //json web token构建
    String token = Jwts.builder()    
            //此处为自定义的、实现org.springframework.security.core.userdetails.UserDetails的类,需要和配置中设置的保持一致
            //此处的subject可以用一个用户名,也可以是多个信息的组合,根据需要来定
            .setSubject(((MyUserDetails) auth.getPrincipal()).getUsername())    
            //设置token过期时间,24小時
            .setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 24 * 1000)) 
            
            //设置token签名、密钥
            .signWith(SignatureAlgorithm.HS512, "MyJwtSecret")       
            
            .compact();  
    
    //返回token
    res.addHeader("Authorization", "Bearer " + token); 
    
    try {
        //登录成功時,返回json格式进行提示
        res.setContentType("application/json;charset=utf-8");
        res.setStatus(HttpServletResponse.SC_OK);
        PrintWriter out = res.getWriter();                  
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("code",HttpServletResponse.SC_OK);
        map.put("message","登陆成功!");
        out.write(new ObjectMapper().writeValueAsString(map));
        out.flush();
        out.close();
    } catch (Exception e1) {
        e1.printStackTrace();
    }
}

}

JWTAuthenticationFilter BasicAuthenticationFilter class implements an interface, the method of the Controller of the need to log in to access has been blocked, not logged in, you can not access, information prompt return JSON

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import com.fasterxml.jackson.databind.ObjectMapper;

import io.jsonwebtoken.Jwts;

/**

  • Whether the login authentication method

  • @author Cheng on life

  • @date 2019年5月26日
    */
    public class JWTAuthenticationFilter extends BasicAuthenticationFilter {

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
    super(authenticationManager);
    }

    /**

    • Filter the request
      * /
      @Override
      protected void doFilterInternal (the HttpServletRequest Request, the HttpServletResponse Response, the FilterChain catena alberghiera) {
      the try {
      // the request header is included in the body of the Authorization
      String header = request.getHeader ( "the Authorization");
      // in the Authorization contains Bearer, when there is a return does not contain direct
      iF (== null || header.startsWith header ( "Bearer")!) {
      the chain.doFilter (Request, Response);
      the responseJSON (Response);
      return;
      }
      // Get permissions failure, throws an exception
      UsernamePasswordAuthenticationToken authentication = getAuthentication (request);
      // after acquisition, the authentication written order after the SecurityContextHolder for use
      SecurityContextHolder.getContext () setAuthentication (authentication). ;
      chain.doFilter(request, response);
      } catch (Exception e) {
      responseJson(response);
      e.printStackTrace();
      }
      }

    /**

    • Tips when not logged in
    • @param response
      */
      private void responseJson(HttpServletResponse response){
      try {
      //未登錄時,使用json進行提示
      response.setContentType(“application/json;charset=utf-8”);
      response.setStatus(HttpServletResponse.SC_FORBIDDEN);
      PrintWriter out = response.getWriter();
      Map<String,Object> map = new HashMap<String,Object>();
      map.put(“code”,HttpServletResponse.SC_FORBIDDEN);
      map.put(“message”,“请登录!”);
      out.write(new ObjectMapper().writeValueAsString(map));
      out.flush();
      out.close();
      } catch (Exception e1) {
      e1.printStackTrace();
      }
      }

    /**

    • By token, obtain user information
    • @param request
    • @return
      */
      private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
      String token = request.getHeader(“Authorization”);
      if (token != null) {
      //通过token解析出用户信息
      String user = Jwts.parser()
      //签名、密钥
      .setSigningKey(“MyJwtSecret”)
      .parseClaimsJws(token.replace("Bearer ", “”))
      .getBody()
      .getSubject();
      //不为null,返回
      if (user != null) {
      return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
      }
      return null;
      }
      return null;
      }

}

Receiving entity class parameters in a log filter may be directly received, that a class is not necessary

public class User {

private long id;
private String username;
private String password;

public long getId() {
    return id;
}

public String getUsername() {
    return username;
}

public void setUsername(String username) {
    this.username = username;
}

public String getPassword() {
    return password;
}

public void setPassword(String password) {
    this.password = password;
}

}

Configuration B project
the first step must be the introduction of shelf package in the pom

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

第二步,增加SecurityConfig配置文件
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;

import com.example.demo.filter.JWTAuthenticationFilter;

/**

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        //关闭跨站请求防护
        .cors().and().csrf().disable()
        //允许不登陆就可以访问的方法,多个用逗号分隔
        .authorizeRequests()
        //其他的需要授权后访问
        .anyRequest().authenticated()
        
        .and()
        //增加是否登陸过滤
        .addFilter(new JWTAuthenticationFilter(authenticationManager()))
        // 前后端分离是无状态的,所以暫時不用session,將登陆信息保存在token中。
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

}

}

The third step in the method of increasing whether to intercept the login filter
Import java.io.PrintWriter;
Import java.util.ArrayList;
Import java.util.HashMap;
Import java.util.Map;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import com.fasterxml.jackson.databind.ObjectMapper;

import io.jsonwebtoken.Jwts;

/**

  • Whether the login authentication method

*/
public class JWTAuthenticationFilter extends BasicAuthenticationFilter {

public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
    super(authenticationManager);
}

/**
 * 對請求進行過濾
 */
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {        
    try {
        //请求体的头中是否包含Authorization
        String header = request.getHeader("Authorization");     
        //Authorization中是否包含Bearer,有一个不包含时直接返回
        if (header == null || !header.startsWith("Bearer ")) {
            chain.doFilter(request, response);
            responseJson(response);
            return;        
        } 
        //获取权限失败,会抛出异常
        UsernamePasswordAuthenticationToken authentication = getAuthentication(request); 
        //获取后,将Authentication写入SecurityContextHolder中供后序使用
        SecurityContextHolder.getContext().setAuthentication(authentication); 
        chain.doFilter(request, response);
    } catch (Exception e) {
        responseJson(response);
        e.printStackTrace();
    }     
}

/**
 * 未登錄時的提示
 * @param response
 */
private void responseJson(HttpServletResponse response){
    try {
        //未登錄時,使用json進行提示
        response.setContentType("application/json;charset=utf-8");
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        PrintWriter out = response.getWriter();                 
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("code",HttpServletResponse.SC_FORBIDDEN);
        map.put("message","请登录!");
        out.write(new ObjectMapper().writeValueAsString(map));
        out.flush();
        out.close();
    } catch (Exception e1) {
        e1.printStackTrace();
    }
}

/**
 * 通过token,获取用户信息
 * @param request
 * @return
 */
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {        
    String token = request.getHeader("Authorization");        
    if (token != null) {            
        //通过token解析出用户信息            
        String user = Jwts.parser()   
                //签名盐
                .setSigningKey("MyJwtSecret")                    
                .parseClaimsJws(token.replace("Bearer ", ""))                    
                .getBody()                    
                .getSubject();     
        //不为null,返回
        if (user != null) {                
            return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());            
        }           
        return null;        
    }        
    return null;    
} 

}

B project from the configuration, it can be seen, B project configuration too simple, only need to look to intercept requests are not logged in, log in even also are saved.
Items A and B were added to a Controller, for testing
Import org.springframework.web.bind.annotation.GetMapping;
Import org.springframework.web.bind.annotation.RestController;

/**

  • Test Case

  • @author Cheng on life

  • @date 2019年5月26日
    */
    @RestController
    public class IndexController {

    @GetMapping("/index")
    public Object index(){

     return "index";
    

    }
    }

Use testing tools to test
the first step, index items A and B test whether the project can access the results can not be accessed, OK Test Results

Here Insert Picture Description
Here Insert Picture Description

Test results -1

Here Insert Picture Description

Test results -2

The second step, by logging obtain token, the login is successful, the prompt return JSON format, returned token in the header, click on the response headers, get token

Here Insert Picture Description

Test results -3

Here Insert Picture Description

Test results -4

The third step is to copy the token to the head A project index, the head of Project B index, the test results ok, can access, you can put token time set shorter, test token expired, and still be able to access .

Here Insert Picture Description

Test results -5

Here Insert Picture Description

Test results -6

Finally, the Token feel the structure, fixed Bearer removed from the front, the back is divided into three parts, separated by a point, this is simple to understand the next bar.

Header (header)
Payload (load)
Signature (Signed)

Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJmZW5nIiwiZXhwIjoxNTU4OTUxMjM5fQ.X7lOhHJljxnVcNEckYSX22rgTDN0ToRJLaPb_1dAoPzx6q_eN5B5iOxO2GXoNUllIfQG6SrdJhgYzKZPTMsDIg

Spring Security integration JWT, to achieve single sign-on function, this came to an end, it seems very simple is not it, then hands it a try.

Guess you like

Origin blog.csdn.net/weixin_44946117/article/details/91854834