Spring Security + JWT for single sign on

Click "Java base" above and select "set as star"

Be a positive person, not a active loser!

Update articles at 14:00 every day, and lose a billion hairs every day

Source code boutique column

Source: DPB bobokaoya sm blog. csdn. net/

article/details/103409430

We introduced the use of SpringSecurity in detail in the previous days' articles. Let's take a look at this article to realize single sign on operation in combination with JWT.

1, What is single sign on

Single Sign On (SSO) is one of the most popular solutions for enterprise business integration. The definition of SSO is that in multiple application systems, users only need to log in once to access all mutually trusted application systems

The background management system + user applet based on spring boot + mybatis Plus + Vue & element supports RBAC dynamic permission, multi tenancy, Data permission, workflow, three-party login, payment, SMS, mall and other functions.

Project address: https://github.com/YunaiV/ruoyi-vue-pro

2, Simple operation mechanism

The mechanism of single sign on is actually relatively simple. A real example is used for comparison. There are many independent scenic spots in a park. Tourists can buy tickets at the gate of each scenic spot. For tourists who need to visit all the scenic spots, this way of buying tickets is very inconvenient. They need to queue up at the gate of each scenic spot to buy tickets. If they take out their wallets, they are easy to lose and are very unsafe. So the vast majority of tourists choose to buy a through ticket (also known as package ticket) at the gate, so they can play all the scenic spots without having to buy tickets again. They only need to show the package tickets they just bought at the gate of each scenic spot to be allowed to enter each independent scenic spot. The mechanism of single sign on is the same, as shown in the following figure,

User authentication: this phase is mainly about the user sending an authentication request to the authentication server. The authentication server returns a successful token to the user, which is mainly completed in the authentication server, that is, the authentication system in the figure. Note that there can only be one authentication system. Identity verification: when a user carries a token to access other servers, the authenticity of the token must be verified in other servers, mainly in the resource server, that is, the application system 23 in the figure

Based on the idea of micro service, the project practice under the B2C e-commerce scenario is constructed. The core technology stack is Spring Boot + Dubbo. In the future, Spring Cloud Alibaba will be reconstituted.

Project address: https://github.com/YunaiV/onemall

3, JWT introduction

Concept description

From the distributed authentication process, it is not difficult to find that token plays the most critical role. The security of token is directly related to the robustness of the system. Here we choose to use JWT to realize the generation and verification of token. JWT, full name JSON Web Token, official website address https://jwt.io , is an excellent distributed identity verification scheme. Tokens can be generated or parsed and verified.

The token generated by JWT consists of three parts:

Header: it mainly sets some specification information. The coding format of the signature part is declared in the header. Payload: the part of the token that stores valid information, such as user name, user role, expiration time, etc., but do not put the password, which will be disclosed! Signature: after the head and load are encoded with base64 respectively, use "." Connect, add salt, and finally code with the code type declared in the header to get the signature.

Security analysis of token generated by JWT

From the composition of the token generated by JWT, to avoid the forgery of the token, we mainly need to look at the signature part. The signature part is composed of three parts, in which the base64 code of the header and load is almost transparent and has no security, so the burden of guarding the security of the token will fall on the added salt! Imagine: if the salt used to generate the token is the same as the salt added when parsing the token. Isn't it similar to the people's Bank of China's disclosure of RMB anti-counterfeiting technology? You can use this salt to parse the token, which can be used to forge the token. At this time, we need to use asymmetric encryption to encrypt the salt, so as to achieve the security effect that the generated token is inconsistent with the salt used to verify the token!

Asymmetric encryption RSA introduction

Basic principle: two keys are generated at the same time: private key and public key. The private key is kept secret. The public key can be distributed to the trusted client for private key encryption. The public key encryption can be decrypted only after holding the private key or public key. The public key encryption can be decrypted only after holding the private key. Advantages: security, difficult to crack disadvantages: the algorithm is time-consuming. In order to be safe, history can be accepted: three mathematicians Rivest, Shamir and Adleman designed an algorithm, Asymmetric encryption can be implemented. The algorithm is abbreviated by the names of three of them: RSA.

4, SpringSecurity integrates JWT

1. analysis of certification ideas

SpringSecurity mainly implements functions through filters! We need to find the SpringSecurity implementation authentication and identity verification filter!

Review the centralized certification process

User authentication: the attemptAuthentication method in the UsernamePasswordAuthenticationFilter is used to implement the authentication function. The successfulAuthentication method in the parent class of the filter implements the operation after successful authentication. Authentication: use the doFilterInternal method in the BasicAuthenticationFilter filter to verify whether to log in to determine whether to enter the subsequent filter.

Analyze distributed authentication process

User authentication: due to distributed projects, most of them are designed with front and back-end architecture. To meet the authentication request parameters that can accept asynchronous post s, we need to modify the attemptAuthentication method in the UsernamePasswordAuthenticationFilter filter so that it can receive the request body. In addition, the default successfulAuthentication method is to put the user information directly into the session after passing the authentication. Now we need to modify this method to generate a token and return it to the user after passing the authentication. Identity verification: the original doFilterInternal method in the BasicAuthenticationFilter filter verifies whether the user logs in, that is, whether there is user information in the session. We need to modify it to verify whether the token carried by the user is legal, parse the user information, and give it to SpringSecurity, so that subsequent authorization functions can be used normally.

2. specific implementation

To demonstrate the effect of single sign on, we designed the following project structure

2.1 parent project creation

Because this case needs to create multiple systems, we use the maven aggregation project to implement it. First, create a parent project and import the springboot parent dependency

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.3.RELEASE</version>
    <relativePath/>
</parent>

2.2 creation of public works

Then create a common project. Other projects depend on this system to import JWT related dependencies

<dependencies>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>0.10.7</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-impl</artifactId>
        <version>0.10.7</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-jackson</artifactId>
        <version>0.10.7</version>
        <scope>runtime</scope>
    </dependency>
    <!--jackson package-->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.9</version>
    </dependency>
    <!--Log package-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-logging</artifactId>
    </dependency>
    <dependency>
        <groupId>joda-time</groupId>
        <artifactId>joda-time</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
</dependencies>

Create related tool classes

Payload

/**
 * @program: springboot-54-security-jwt-demo
 * @description:
 * @author: Bobo roast duck
 * @create: 2019-12-03 10:28
 */
@Data
public class Payload <T>{
    private String id;
    private T userInfo;
    private Date expiration;
}

JsonUtils

package com.dpb.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.List;
import java.util.Map;

/**
 * @author: Bobo roast duck
 **/
public class JsonUtils {

    public static final ObjectMapper mapper = new ObjectMapper();

    private static final Logger logger = LoggerFactory.getLogger(JsonUtils.class);

    public static String toString(Object obj) {
        if (obj == null) {
            return null;
        }
        if (obj.getClass() == String.class) {
            return (String) obj;
        }
        try {
            return mapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            logger.error("json Serialization error:" + obj, e);
            return null;
        }
    }

    public static <T> T toBean(String json, Class<T> tClass) {
        try {
            return mapper.readValue(json, tClass);
        } catch (IOException e) {
            logger.error("json Parsing error:" + json, e);
            return null;
        }
    }

    public static <E> List<E> toList(String json, Class<E> eClass) {
        try {
            return mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, eClass));
        } catch (IOException e) {
            logger.error("json Parsing error:" + json, e);
            return null;
        }
    }

    public static <K, V> Map<K, V> toMap(String json, Class<K> kClass, Class<V> vClass) {
        try {
            return mapper.readValue(json, mapper.getTypeFactory().constructMapType(Map.class, kClass, vClass));
        } catch (IOException e) {
            logger.error("json Parsing error:" + json, e);
            return null;
        }
    }

    public static <T> T nativeRead(String json, TypeReference<T> type) {
        try {
            return mapper.readValue(json, type);
        } catch (IOException e) {
            logger.error("json Parsing error:" + json, e);
            return null;
        }
    }
}

JwtUtils

package com.dpb.utils;

import com.dpb.domain.Payload;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.joda.time.DateTime;

import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Base64;
import java.util.UUID;

/**
 * @author: Bobo roast duck
 * Methods for generating and verifying token s
 */
public class JwtUtils {

    private static final String JWT_PAYLOAD_USER_KEY = "user";

    /**
     * Private key encryption token
     *
     * @param userInfo   Data in load
     * @param privateKey Private key
     * @param expire     Expiration time in minutes
     * @return JWT
     */
    public static String generateTokenExpireInMinutes(Object userInfo, PrivateKey privateKey, int expire) {
        return Jwts.builder()
                .claim(JWT_PAYLOAD_USER_KEY, JsonUtils.toString(userInfo))
                .setId(createJTI())
                .setExpiration(DateTime.now().plusMinutes(expire).toDate())
                .signWith(privateKey, SignatureAlgorithm.RS256)
                .compact();
    }

    /**
     * Private key encryption token
     *
     * @param userInfo   Data in load
     * @param privateKey Private key
     * @param expire     Expiration time in seconds
     * @return JWT
     */
    public static String generateTokenExpireInSeconds(Object userInfo, PrivateKey privateKey, int expire) {
        return Jwts.builder()
                .claim(JWT_PAYLOAD_USER_KEY, JsonUtils.toString(userInfo))
                .setId(createJTI())
                .setExpiration(DateTime.now().plusSeconds(expire).toDate())
                .signWith(privateKey, SignatureAlgorithm.RS256)
                .compact();
    }

    /**
     * Public key parsing token
     *
     * @param token     token in user request
     * @param publicKey Public key
     * @return Jws<Claims>
     */
    private static Jws<Claims> parserToken(String token, PublicKey publicKey) {
        return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
    }

    private static String createJTI() {
        return new String(Base64.getEncoder().encode(UUID.randomUUID().toString().getBytes()));
    }

    /**
     * Get the user information in the token
     *
     * @param token     Token in user request
     * @param publicKey Public key
     * @return User information
     */
    public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey, Class<T> userType) {
        Jws<Claims> claimsJws = parserToken(token, publicKey);
        Claims body = claimsJws.getBody();
        Payload<T> claims = new Payload<>();
        claims.setId(body.getId());
        claims.setUserInfo(JsonUtils.toBean(body.get(JWT_PAYLOAD_USER_KEY).toString(), userType));
        claims.setExpiration(body.getExpiration());
        return claims;
    }

    /**
     * Get the load information in the token
     *
     * @param token     Token in user request
     * @param publicKey Public key
     * @return User information
     */
    public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey) {
        Jws<Claims> claimsJws = parserToken(token, publicKey);
        Claims body = claimsJws.getBody();
        Payload<T> claims = new Payload<>();
        claims.setId(body.getId());
        claims.setExpiration(body.getExpiration());
        return claims;
    }
}

RsaUtils

package com.dpb.utils;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * @author Bobo roast duck
 */
public class RsaUtils {

    private static final int DEFAULT_KEY_SIZE = 2048;
    /**
     * Read public key from file
     *
     * @param filename Public key save path, relative to classpath
     * @return Public key object
     * @throws Exception
     */
    public static PublicKey getPublicKey(String filename) throws Exception {
        byte[] bytes = readFile(filename);
        return getPublicKey(bytes);
    }

    /**
     * Read key from file
     *
     * @param filename Private key saving path, relative to classpath
     * @return Private key object
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(String filename) throws Exception {
        byte[] bytes = readFile(filename);
        return getPrivateKey(bytes);
    }

    /**
     * Get public key
     *
     * @param bytes Byte form of public key
     * @return
     * @throws Exception
     */
    private static PublicKey getPublicKey(byte[] bytes) throws Exception {
        bytes = Base64.getDecoder().decode(bytes);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePublic(spec);
    }

    /**
     * Get key
     *
     * @param bytes Byte form of private key
     * @return
     * @throws Exception
     */
    private static PrivateKey getPrivateKey(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
        bytes = Base64.getDecoder().decode(bytes);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePrivate(spec);
    }

    /**
     * According to the ciphertext, the rsa public key and private key are saved and written to the specified file
     *
     * @param publicKeyFilename  Public key file path
     * @param privateKeyFilename Private key file path
     * @param secret             Generate ciphertext of key
     */
    public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret, int keySize) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        SecureRandom secureRandom = new SecureRandom(secret.getBytes());
        keyPairGenerator.initialize(Math.max(keySize, DEFAULT_KEY_SIZE), secureRandom);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        //Get public key and write out
        byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
        publicKeyBytes = Base64.getEncoder().encode(publicKeyBytes);
        writeFile(publicKeyFilename, publicKeyBytes);
        //Get private key and write out
        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
        privateKeyBytes = Base64.getEncoder().encode(privateKeyBytes);
        writeFile(privateKeyFilename, privateKeyBytes);
    }

    private static byte[] readFile(String fileName) throws Exception {
        return Files.readAllBytes(new File(fileName).toPath());
    }

    private static void writeFile(String destPath, byte[] bytes) throws IOException {
        File dest = new File(destPath);
        if (!dest.exists()) {
            dest.createNewFile();
        }
        Files.write(dest.toPath(), bytes);
    }
}

Write test classes in general sub modules to generate rsa public and private keys

/**
 * @program: springboot-54-security-jwt-demo
 * @description:
 * @author: Bobo roast duck
 * @create: 2019-12-03 11:08
 */
public class JwtTest {
    private String privateKey = "c:/tools/auth_key/id_key_rsa";

    private String publicKey = "c:/tools/auth_key/id_key_rsa.pub";

    @Test
    public void test1() throws Exception{
        RsaUtils.generateKey(publicKey,privateKey,"dpb",1024);
    }

}

2.3 creation of certification system

Next, we create our authentication service.

Import related dependencies

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <artifactId>security-jwt-common</artifactId>
        <groupId>com.dpb</groupId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.0</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.10</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

create profile

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/srm
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
mybatis:
  type-aliases-package: com.dpb.domain
  mapper-locations: classpath:mapper/*.xml
logging:
  level:
    com.dpb: debug
rsa:
  key:
    pubKeyFile: c:\tools\auth_key\id_key_rsa.pub
    priKeyFile: c:\tools\auth_key\id_key_rsa

Configuration class that provides public and private keys

package com.dpb.config;

import com.dpb.utils.RsaUtils;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.security.PrivateKey;
import java.security.PublicKey;

/**
 * @program: springboot-54-security-jwt-demo
 * @description:
 * @author: Bobo roast duck
 * @create: 2019-12-03 11:25
 */
@Data
@ConfigurationProperties(prefix = "rsa.key")
public class RsaKeyProperties {

    private String pubKeyFile;
    private String priKeyFile;

    private PublicKey publicKey;
    private PrivateKey privateKey;

    /**
     * Triggered when the system starts
     * @throws Exception
     */
    @PostConstruct
    public void createRsaKey() throws Exception {
        publicKey = RsaUtils.getPublicKey(pubKeyFile);
        privateKey = RsaUtils.getPrivateKey(priKeyFile);
    }

}

Create startup class

/**
 * @program: springboot-54-security-jwt-demo
 * @description: Startup class
 * @author: Bobo roast duck
 * @create: 2019-12-03 11:23
 */
@SpringBootApplication
@MapperScan("com.dpb.mapper")
@EnableConfigurationProperties(RsaKeyProperties.class)
public class App {

    public static void main(String[] args) {
        SpringApplication.run(App.class,args);
    }
}

Logic to complete data authentication

pojo

package com.dpb.domain;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;

/**
 * @program: springboot-54-security-jwt-demo
 * @description:
 * @author: Bobo roast duck
 * @create: 2019-12-03 15:21
 */
@Data
public class RolePojo implements GrantedAuthority {

    private Integer id;
    private String roleName;
    private String roleDesc;

    @JsonIgnore
    @Override
    public String getAuthority() {
        return roleName;
    }
}
package com.dpb.domain;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * @program: springboot-54-security-jwt-demo
 * @description:
 * @author: Bobo roast duck
 * @create: 2019-12-03 11:33
 */
@Data
public class UserPojo implements UserDetails {

    private Integer id;

    private String username;

    private String password;

    private Integer status;

    private List<RolePojo> roles;

    @JsonIgnore
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> auth = new ArrayList<>();
        auth.add(new SimpleGrantedAuthority("ADMIN"));
        return auth;
    }

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

    @Override
    public String getUsername() {
        return this.username;
    }
    @JsonIgnore
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    @JsonIgnore
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    @JsonIgnore
    @Override
    public boolean isEnabled() {
        return true;
    }
}

Mapper interface

public interface UserMapper {
    public UserPojo queryByUserName(@Param("userName") String userName);
}

Mapper mapping file

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dpb.mapper.UserMapper">
    <select id="queryByUserName" resultType="UserPojo">
        select * from t_user where username = #{userName}
    </select>
</mapper>

Service

public interface UserService extends UserDetailsService {

}
@Service
@Transactional
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper mapper;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        UserPojo user = mapper.queryByUserName(s);

        return user;
    }
}

Custom authentication filter

package com.dpb.filter;

import com.dpb.config.RsaKeyProperties;
import com.dpb.domain.RolePojo;
import com.dpb.domain.UserPojo;
import com.dpb.utils.JwtUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.bytebuddy.agent.builder.AgentBuilder;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @program: springboot-54-security-jwt-demo
 * @description:
 * @author: Bobo roast duck
 * @create: 2019-12-03 11:57
 */
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;
    private RsaKeyProperties prop;

    public TokenLoginFilter(AuthenticationManager authenticationManager, RsaKeyProperties prop) {
        this.authenticationManager = authenticationManager;
        this.prop = prop;
    }

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {
            UserPojo sysUser = new ObjectMapper().readValue(request.getInputStream(), UserPojo.class);

            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword());
            return authenticationManager.authenticate(authRequest);
        }catch (Exception e){
            try {
                response.setContentType("application/json;charset=utf-8");
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                PrintWriter out = response.getWriter();
                Map resultMap = new HashMap();
                resultMap.put("code", HttpServletResponse.SC_UNAUTHORIZED);
                resultMap.put("msg", "Wrong user name or password!");
                out.write(new ObjectMapper().writeValueAsString(resultMap));
                out.flush();
                out.close();
            }catch (Exception outEx){
                outEx.printStackTrace();
            }
            throw new RuntimeException(e);
        }
    }

    public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        UserPojo user = new UserPojo();
        user.setUsername(authResult.getName());
        user.setRoles((List<RolePojo>)authResult.getAuthorities());
        String token = JwtUtils.generateTokenExpireInMinutes(user, prop.getPrivateKey(), 24 * 60);
        response.addHeader("Authorization", "Bearer "+token);
        try {
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_OK);
            PrintWriter out = response.getWriter();
            Map resultMap = new HashMap();
            resultMap.put("code", HttpServletResponse.SC_OK);
            resultMap.put("msg", "Certification passed!");
            out.write(new ObjectMapper().writeValueAsString(resultMap));
            out.flush();
            out.close();
        }catch (Exception outEx){
            outEx.printStackTrace();
        }
    }
}

Custom verification token filter

package com.dpb.filter;

import com.dpb.config.RsaKeyProperties;
import com.dpb.domain.Payload;
import com.dpb.domain.UserPojo;
import com.dpb.utils.JwtUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
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 javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * @program: springboot-54-security-jwt-demo
 * @description:
 * @author: Bobo roast duck
 * @create: 2019-12-03 12:39
 */
public class TokenVerifyFilter  extends BasicAuthenticationFilter {
    private RsaKeyProperties prop;

    public TokenVerifyFilter(AuthenticationManager authenticationManager, RsaKeyProperties prop) {
        super(authenticationManager);
        this.prop = prop;
    }

    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String header = request.getHeader("Authorization");
        if (header == null || !header.startsWith("Bearer ")) {
            //If the wrong token is carried, the user will be prompted to log in!
            chain.doFilter(request, response);
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            PrintWriter out = response.getWriter();
            Map resultMap = new HashMap();
            resultMap.put("code", HttpServletResponse.SC_FORBIDDEN);
            resultMap.put("msg", "Please login!");
            out.write(new ObjectMapper().writeValueAsString(resultMap));
            out.flush();
            out.close();
        } else {
            //If you carry a token in the correct format, you need to get the token first
            String token = header.replace("Bearer ", "");
            //Verify whether tken is correct
            Payload<UserPojo> payload = JwtUtils.getInfoFromToken(token, prop.getPublicKey(), UserPojo.class);
            UserPojo user = payload.getUserInfo();
            if(user!=null){
                UsernamePasswordAuthenticationToken authResult = new UsernamePasswordAuthenticationToken(user.getUsername(), null, user.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authResult);
                chain.doFilter(request, response);
            }
        }
    }

}

###Writing configuration classes for SpringSecurity

package com.dpb.config;

import com.dpb.filter.TokenLoginFilter;
import com.dpb.filter.TokenVerifyFilter;
import com.dpb.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
 * @program: springboot-54-security-jwt-demo
 * @description:
 * @author: Bobo roast duck
 * @create: 2019-12-03 12:41
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled=true)
public class WebSecurityConfig   extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userService;

    @Autowired
    private RsaKeyProperties prop;

    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    //Specify the source of the authentication object
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    }
    //SpringSecurity configuration information
    public void configure(HttpSecurity http) throws Exception {
        http.csrf()
                .disable()
                .authorizeRequests()
                .antMatchers("/user/query").hasAnyRole("ADMIN")
                .anyRequest()
                .authenticated()
                .and()
                .addFilter(new TokenLoginFilter(super.authenticationManager(), prop))
                .addFilter(new TokenVerifyFilter(super.authenticationManager(), prop))
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
}

Start service test

Start service

Accessing tests through Postman

We access other resources according to the token information

2.4 resource system creation

It shows that there can be many resource services. Here, we only take the product service as an example. Remember that the resource service can only be authenticated through public key verification. Cannot issue token! Create the product service and import the jar package. Just import the package according to the actual business. We will be the same as the authentication service for the time being.

Next, we create a resource service

Import related dependencies

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <artifactId>security-jwt-common</artifactId>
        <groupId>com.dpb</groupId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.0</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.10</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

Writing a product service profile

Remember that there can only be public key addresses here!

server:
  port: 9002
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/srm
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
mybatis:
  type-aliases-package: com.dpb.domain
  mapper-locations: classpath:mapper/*.xml
logging:
  level:
    com.dpb: debug
rsa:
  key:
    pubKeyFile: c:\tools\auth_key\id_key_rsa.pub

Write a configuration class to read the public key

package com.dpb.config;

import com.dpb.utils.RsaUtils;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

import javax.annotation.PostConstruct;
import java.security.PrivateKey;
import java.security.PublicKey;

/**
 * @program: springboot-54-security-jwt-demo
 * @description:
 * @author: Bobo roast duck
 * @create: 2019-12-03 11:25
 */
@Data
@ConfigurationProperties(prefix = "rsa.key")
public class RsaKeyProperties {

    private String pubKeyFile;

    private PublicKey publicKey;

    /**
     * Triggered when the system starts
     * @throws Exception
     */
    @PostConstruct
    public void createRsaKey() throws Exception {
        publicKey = RsaUtils.getPublicKey(pubKeyFile);
    }

}

Write startup class

package com.dpb;

import com.dpb.config.RsaKeyProperties;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

/**
 * @program: springboot-54-security-jwt-demo
 * @description:
 * @author: Bobo roast duck
 * @create: 2019-12-03 17:23
 */
@SpringBootApplication
@MapperScan("com.dpb.mapper")
@EnableConfigurationProperties(RsaKeyProperties.class)
public class App {

    public static void main(String[] args) {
        SpringApplication.run(App.class,args);
    }
}

Copy the user object, role object and verification authentication interface in the authentication service

Copy the relevant contents of the authentication service

Copy the SpringSecurity configuration class in the authentication service for modification

package com.dpb.config;

import com.dpb.filter.TokenVerifyFilter;
import com.dpb.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
 * @program: springboot-54-security-jwt-demo
 * @description:
 * @author: Bobo roast duck
 * @create: 2019-12-03 12:41
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled=true)
public class WebSecurityConfig   extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userService;

    @Autowired
    private RsaKeyProperties prop;

    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    //Specify the source of the authentication object
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    }
    //SpringSecurity configuration information
    public void configure(HttpSecurity http) throws Exception {
        http.csrf()
                .disable()
                .authorizeRequests()
                //.antMatchers("/user/query").hasAnyRole("USER")
                .anyRequest()
                .authenticated()
                .and()
                .addFilter(new TokenVerifyFilter(super.authenticationManager(), prop))
                //Disable session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
}

Remove "add custom authentication filter"!

Write product processor

package com.dpb.controller;

import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @program: springboot-54-security-jwt-demo
 * @description:
 * @author: Bobo roast duck
 * @create: 2019-12-03 11:55
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/query")
    public String query(){
        return "success";
    }

    @RequestMapping("/update")
    public String update(){
        return "update";
    }
}

test

Done~

Welcome to my knowledge planet to discuss architecture and exchange source code. To add, long press the QR code below:

Tags: Java Spring Boot

Posted by codebuilder on Wed, 01 Jun 2022 23:31:33 +0530