Spring Security OAuth2 JWT SSO integration project

preface

This project is the Spring Security OAuth2 JWT SSO integration project. For those who have a certain understanding of Spring Security, OAuth2 and JWT SSO and want to integrate them organically, the project is written according to the principle of easiest to get started and most complete basic functions. Database – based authentication and authentication. Using OAuth2 authentication, according to the authentication server query user information, authentication processing, according to the permission in the resource server authentication. The permission authentication mode is adopted in this mode. Based on the user’s permission, access the resource server range. [There is also role-based authentication, but the same as before]

for (int i = 0; i <= 2; i++) {

System.out.println(” 下载 source code to eat better! The source code is given at the bottom of the article! );

}

The source code has been annotated in detail and can be understood in conjunction with the article.

For example, a little understanding of Oauth2 authentication, website using wechat authentication process :(from top to bottom according to the direction of the arrow)

First, the use of steps

1. Engineering structure

2. Add dependencies

pom.xml


      
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1. RELEASE</version>
        <relativePath/> <! -- lookup parent from repository -->
    </parent>
    <groupId>com.xxxx</groupId>
    <artifactId>springsecurityoauth2-demo</artifactId>
    <version>0.0.1 - the SNAPSHOT</version>
    <name>springsecurityoauth2-demo</name>
    <description>Demo project for Spring Boot</description>
    <! -- Set spring Cloud and JDK version variables -->
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
    </properties>
    <dependencies>
        <! Oauth2 and Security use components in Spring Cloud -->
        <! -- This will only take effect if spring Cloud dependencies and versions are introduced.
        <dependency>
            <! - oauth2 dependence - >
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <! - security dependence - >
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>
        <dependency>
            <! - web depend on -- -- >
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <! - the test relies on - >
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
            <! - JWT dependence - >
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok
            </groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
        </dependency>
        <! MyBatis -- Starter -- MyBatis -- Starter -- Starter -- Starter -- Starter -- Starter -- Starter -- Starter -- Starter -- Starter -- Starter -- Starter -- Starter -- Starter -- Starter -- Starter -- Starter -- Starter -- Starter -- Starter -- Starter -- Starter -- Starter -- Starter -- Starter -- Starter -- Starter -- Starter -- Starter -- Starter
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <! Mysql-connector-java is a JDBC driver package provided by mysql. This jar package must be used to connect to mysql database using JDBC.
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
    <! DependencyManagement Introduces Spring Cloud dependencies using version variables. -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <! -- Mybatis -generator is a plugin for mybatis to generate entity code automatically.
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.7</version>
                <configuration>
                    <verbose>true</verbose>
                    <overwrite>true</overwrite>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
Copy the code

3. Database construction sentences

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for um_t_role
-- ----------------------------
DROP TABLE IF EXISTS `um_t_role`;
CREATE TABLE `um_t_role`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `created_time` bigint(0) NOT NULL,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `role` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL.PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of um_t_role
-- ----------------------------
INSERT INTO `um_t_role` VALUES (1.'Administrator has access to all interfaces'.1627199362.'Administrator'.'ADMIN');
INSERT INTO `um_t_role` VALUES (2.'Generally have the permission to view the user list and change the password, do not have the permission to add, delete and change the user'.1627199362.'Ordinary user'.'USER');

-- ----------------------------
-- Table structure for um_t_role_user
-- ----------------------------
DROP TABLE IF EXISTS `um_t_role_user`;
CREATE TABLE `um_t_role_user`  (
  `role_id` int(0) NULL DEFAULT NULL,
  `user_id` int(0) NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of um_t_role_user
-- ----------------------------
INSERT INTO `um_t_role_user` VALUES (1.1);

-- ----------------------------
-- Table structure for um_t_user
-- ----------------------------
DROP TABLE IF EXISTS `um_t_user`;
CREATE TABLE `um_t_user`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `account` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL.PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of um_t_user
-- ----------------------------
INSERT INTO `um_t_user` VALUES (1.'admin'.'System Default Administrator'.'$2a$10$N97RyMYeQ7aVTxLvdxq5NeBivdbj/u2GQtHERISUt8qhKBfnjSC1q'.'admin');
INSERT INTO `um_t_user` VALUES (2.'user'.'Ordinary user'.'$2a$10$N97RyMYeQ7aVTxLvdxq5NeBivdbj/u2GQtHERISUt8qhKBfnjSC1q'.'user');
INSERT INTO `um_t_user` VALUES (3.'user'.'test user'.'$2a$10$N97RyMYeQ7aVTxLvdxq5NeBivdbj/u2GQtHERISUt8qhKBfnjSC1q'.'Jacks');

SET FOREIGN_KEY_CHECKS = 1;
Copy the code

== All user passwords are 123456==

4. Prepare configuration files

(1) GeneratorConfig. XML is the configuration file of Mybatis – Generator


      
<! DOCTYPEgeneratorConfiguration
  PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
  "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
  <! -- Generatorconfig. XML configuration file in resource directory -->
  <! -- Database driven Personal Configuration -->
  <classPathEntry
    location="D: \ maven repo/mysql/mysql - connector - Java \ 8.0.18 \ mysql connector - Java - 8.0.18. Jar"/>
  <context id="MysqlTables" targetRuntime="MyBatis3">
    <property name="autoDelimitKeywords" value="true"/>
    <! SQL > select * from ' 'select * from' ';
    <property name="beginningDelimiter" value="`"/>
    <property name="endingDelimiter" value="`"/>
    <! -- optional, controls annotation when creating class -->
    <commentGenerator>
      <property name="suppressDate" value="true"/>
      <property name="suppressAllComments" value="true"/>
    </commentGenerator>
    <! Database link address account password -->
    <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
      connectionURL="JDBC: mysql: / / 127.0.0.1:3306 / auth_test? useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"
      userId="root"
      password="123456">
      <property name="nullCatalogMeansCurrent" value="true"/>
    </jdbcConnection>
    <! Optional, type handler, conversion control between database type and Java type
    <javaTypeResolver>
      <property name="forceBigDecimals" value="false"/>
    </javaTypeResolver>
    <! Create Model class location -->
    <javaModelGenerator targetPackage="com.xxxx.springsecurityoauth2demo.model.pojo"
      targetProject="src/main/java">
      <! - whether to allow package, namely the targetPackage. SchemaName. TableName -- -- >
      <property name="enableSubPackages" value="true"/>
      <! Whether to trim columns of type CHAR -->
      <property name="trimStrings" value="true"/>
      <! The generated Model object will not have setter methods, only constructor methods.
      <property name="immutable" value="false"/>
    </javaModelGenerator>
    <! Create mapper file location -->
    <sqlMapGenerator targetPackage="mappers" targetProject="src/main/resources">
      <property name="enableSubPackages" value="true"/>
    </sqlMapGenerator>
    <! Create Dao class location -->
    <javaClientGenerator type="XMLMAPPER" targetPackage="com.xxxx.springsecurityoauth2demo.model.dao"
      targetProject="src/main/java">
      <property name="enableSubPackages" value="true"/>
    </javaClientGenerator>
    <! SQL > select * from user where user name = 'user';
    <table schema="root" tableName="um_t_role" domainObjectName="Role"
      enableCountByExample="false"
      enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
      selectByExampleQueryId="false">
    </table>
    <table schema="root" tableName="um_t_user" domainObjectName="User" enableCountByExample="false"
      enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
      selectByExampleQueryId="false">
    </table>

  </context>
</generatorConfiguration>
Copy the code

(2)application.properties

server.port=8080

spring.datasource.url=jdbc:mysql://localhost:3306/auth_test? useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456

mybatis.mapper-locations=classpath:mappers/*.xml
Copy the code

5. Use Mybatis – Generator to generate entity classes, Mapper and XML documents.

6.Security, OAuth2, JWT, SSO configuration classes

(1) Authorization server

Used for authorization configuration. We need to inherit AuthorizationServerConfigurerAdapter class, rewrite the configure () method.

AuthorizationServerConfig.java

package com.xxxx.springsecurityoauth2demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/** * Description: Authorization server@EnableAuthorizationServer, extends to simulate AuthorizationServerConfigurerAdapter * and license server and server resources together, normally are decoupled. * /
@Configuration
@EnableAuthorizationServer // Start the authorization server
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Resource
    private PasswordEncoder passwordEncoder;
    @Resource
    private AuthenticationManager authenticationManager;
    @Resource
    private UserDetailsService userDetailsService;
    @Resource(name = "jwtTokenStore")
    private TokenStore tokenStore;
    @Resource(name = "jwtAccessTokenConverter")
    private JwtAccessTokenConverter jwtAccessTokenConverter;
    @Resource
    private JwtTokenEnhancer jwtTokenEnhancer;

    /** * Configure the password authorization mode **@param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //TokenEnhancerChain is an implementation of TokenEnhance
        TokenEnhancerChain chain = new TokenEnhancerChain();
        List<TokenEnhancer> delegates = new ArrayList<>();
        delegates.add(jwtTokenEnhancer);
        delegates.add(jwtAccessTokenConverter);// Also put the converter in to implement the jwtTokenEnhancer conversion
        chain.setTokenEnhancers(delegates);
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService)
                JwtAccessTokenConverter JWT access token converter and JwtTokenStore JWT token store
                / / by AuthorizationServerEndpointsConfigurer authorization server endpoint configuration to join two instances
                .tokenStore(tokenStore)
                .accessTokenConverter(jwtAccessTokenConverter)
                .tokenEnhancer(chain); // Set the JWT enhancement


    }

    /** * Authorization configuration **@param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        * (client) * (client) * (client) * (client) * (client) * (client) * (client) * (client

        clients.inMemory() //.inmemory () puts it into memory. For convenience, we directly generated client in memory. Normally, it will be processed when we take the initiative to register with the authorization server.
                .withClient("client") // Specify the client. The parameter is the ID of a unique client
                .secret(passwordEncoder.encode("112233")) // Specify the key
                .redirectUris("http://www.baidu.com") // Specify the redirection address to get the authorization code.
                / /. RedirectUris (" http://localhost:8081/login ") / / single sign-on to another server
                .accessTokenValiditySeconds(60 * 10) // Set the Access Token expiration time
                .refreshTokenValiditySeconds(60 * 60 * 24) // Set the refresh Token expiration time
                .scopes("all") // Specify the scope of authorization
                .autoApprove(true) // Automatic authorization is not required
                /** * Authorization type: * "authorization_code" authorization code mode * "password" password mode * "refresh_token" refreshing token */
                .authorizedGrantTypes("authorization_code"."password"."refresh_token"); // Specify the authorization type. Multiple authorization types can coexist.

    }

    /** * Single sign-on configuration **@param security
     * @throws Exception
     */
    / * @ Override public void the configure (AuthorizationServerSecurityConfigurer security) throws the Exception {/ / must be identity authentication, Sso must be configured with security.tokenkeyAccess ("isAuthenticated()"); } * /
}
 

Copy the code

(2) Resource manager

The authorization server and resource server in enterprise production are two separate servers, which we put together because we used a single Model project for learning.

ResourceServerConfig.java

package com.xxxx.springsecurityoauth2demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;

/** * Description: Resource manager@EnableResourceServer, extends to simulate ResourceServerConfigurerAdapter * and license server and server resources together, normally are decoupled. * /
@Configuration
@EnableResourceServer // Start resource server
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
         http.requestMatchers().antMatchers("/api/**").and()
                .authorizeRequests()// Request for authorization
                // Performs interface authentication
                .antMatchers("/api/user/save").hasAuthority("admin")
                // Other interfaces do not need authentication, only authentication is required.anyRequest() .authenticated(); }}Copy the code

(3)JWT content enhancer

JwtTokenEnhancer.java

package com.xxxx.springsecurityoauth2demo.config;

import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;

import java.util.HashMap;
import java.util.Map;

/** * Config JwtTokenEnhancer add custom information, inherit TokenEnhancer implement a JWT content enhancer */
public class JwtTokenEnhancer implements TokenEnhancer {
    /** * JWT content enhancer *@param oAuth2AccessToken
     * @param oAuth2Authentication
     * @return* /
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
        Map<String, Object> info = new HashMap();
        info.put("enhance"."Enhanced message");
        // The AccessToken of oAuth2 is given as an argument, the implementation class is DefaultOAuth2AccessToken,
        // There is a setAdditionalInformation method to add custom information (Map type)
        ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(info);
        returnoAuth2AccessToken; }}Copy the code

(4) TokenStore configuration class

JwtTokenStoreConfig.java

package com.xxxx.springsecurityoauth2demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

/** * Description: TokenStore configuration class. * TokenStore implementation classes, including InMemoryTokenStore, JdbcTokenStore, JwtTokenStore, RedisTokenStore. * JwtAccessTokenConverter JWT access token converter and JwtTokenStore JWT token storage component */
@Configuration
public class JwtTokenStoreConfig {
    /** * generate TokenStore to store tokens@return TokenStore
     */
    @Bean
    public TokenStore jwtTokenStore(a) {
        // We need to pass in JwtAccessTokenConverter
        return new JwtTokenStore(jwtAccessTokenConverter());
    }
    /** * Generates the JwtAccessTokenConverter and sets the key *@return JwtAccessTokenConverter
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(a) {
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        // Set the JWT key
        jwtAccessTokenConverter.setSigningKey("test_key");
        return jwtAccessTokenConverter;
    }

    /** * JwtTokenEnhancer injection *@return* /
    @Bean
    public JwtTokenEnhancer jwtTokenEnhancer(a) {
        return newJwtTokenEnhancer(); }}Copy the code

(5)Security core configuration class

SecurityConfig.java

package com.xxxx.springsecurityoauth2demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import javax.annotation.Resource;

1. Rewrite configure(HttpSecurity HTTP) * 2. Configure Ioc injection for PasswordEncoder. * /
@Configuration
@EnableWebSecurity // Enable Web Security
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // Configure no permission to access the redirect custom page
        http.exceptionHandling().accessDeniedPage("/unauth.html");

        http.authorizeRequests()
                // Permits several endpoint requests, login requests, and logout requests from the authorization server.
                .antMatchers("/oauth/**"."/login/**"."/logout/**")
                .permitAll()
                .anyRequest()
                .authenticated()
                //. And () is equivalent to going back to HTTP and continuing the configuration
                .and()
                // Allow all form requests
                .formLogin()
                .permitAll()
                .and()
                / / close CSRF
                .csrf().disable();

    }

    /** * The AuthenticationManager class used in password authorization mode **@return
     * @throws Exception
     */
    @Override
    @Bean
    protected AuthenticationManager authenticationManager(a) throws Exception {
        return super.authenticationManager();
    }


    @Bean
    public PasswordEncoder passwordEncoder(a) {
        return newBCryptPasswordEncoder(); }}Copy the code

7. Configure the UserDetailService of Spring Security to query information from the database for authentication

(1) MyUserService interface

package com.xxxx.springsecurityoauth2demo.service;
/** * Description: MyUserService interface */
public interface MyUserService{}Copy the code

(2)MyUserServiceImpl

package com.xxxx.springsecurityoauth2demo.service.impl;


import com.xxxx.springsecurityoauth2demo.model.pojo.SecurityUser;
import com.xxxx.springsecurityoauth2demo.model.pojo.User;
import com.xxxx.springsecurityoauth2demo.service.MyUserService;
import com.xxxx.springsecurityoauth2demo.service.UserService;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/** * Description: Custom UserDetailsService implementation class **
@Service  // Please login is reporting incorrect user name and password because there is no Service annotation!!
public class MyUserServiceImpl implements UserDetailsService.MyUserService {

    @Resource
    private PasswordEncoder passwordEncoder;
    @Resource
    private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userService.getUserByUserName(username);
        String name = user.getName();
        String password = user.getPassword();
        String authority = user.getAccount();
        return newSecurityUser(name, password, AuthorityUtils.commaSeparatedStringToAuthorityList(authority)); }}Copy the code

(3) Customize the User entity of the Security framework

SecurityUser.java

package com.xxxx.springsecurityoauth2demo.model.pojo;

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

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

/** * Description: Customize the User entity of the Security framework */
public class SecurityUser implements UserDetails {

    private String username;
    private String password;
    private List<GrantedAuthority> authorities;

    public SecurityUser(String username, String password, List<GrantedAuthority> authorities) {
        this.username = username;
        this.password = password;
        this.authorities = authorities;
    }

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

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

    @Override
    public String getUsername(a) {
        return username;
    }

    @Override
    public boolean isAccountNonExpired(a) {
        return true;
    }

    @Override
    public boolean isAccountNonLocked(a) {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired(a) {
        return true;
    }

    @Override
    public boolean isEnabled(a) {
        return true; }}Copy the code

8. The Controller layer

(1) Controller for JWT Token test

TestUserController.java

package com.xxxx.springsecurityoauth2demo.controller;


import io.jsonwebtoken.Jwts;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;

/** * Description: UserController is used to simulate the resource server, used to access resources. * /
@RestController
@RequestMapping("/user")
public class TestUserController {

    // Do not connect to the database
    @RequestMapping("/getCurrentUser")
    public Object getCurrentUser(Authentication authentication, HttpServletRequest request) {
        //Authorization is an attribute in the request header.
        String header = request.getHeader("Authorization");
        // Bearer: JWT token
        String token = header.substring(header.lastIndexOf("bearer") + 7);
        return Jwts.parser()    
                .setSigningKey("test_key".getBytes(StandardCharsets.UTF_8))// Specify the encoding format, otherwise the token will have a Chinese conversion exception.parseClaimsJws(token) .getBody(); }}Copy the code

(2)User Resource server UserController

UserService.java

package com.xxxx.springsecurityoauth2demo.controller;

import com.xxxx.springsecurityoauth2demo.model.req.ReqUser;
import com.xxxx.springsecurityoauth2demo.service.UserService;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

/** * Description: User resource server UserController */
@RestController
@RequestMapping("/api/user")
public class UserController {
    @Resource
    private UserService userService;

    /** * get all user list, all permissions can access *@return* /
    @PostMapping("/users")
    public Object getAllUsers(a) {
        return userService.getAllUsers();
    }

    /** * Add a user. Only admin users can access */
    @PostMapping("/save")
    public Object save(@RequestBody ReqUser reqUser) {
        returnuserService.save(reqUser); }}Copy the code

9. The Service layer

(1) the UserService interface

UserService.java

package com.xxxx.springsecurityoauth2demo.service;

import com.xxxx.springsecurityoauth2demo.model.pojo.User;
import com.xxxx.springsecurityoauth2demo.model.req.ReqUser;

/** * Description: UserService interface */
public interface UserService {
    Object getAllUsers(a);

    User getUserByUserName(String username);

    Object save(ReqUser reqUser);
}
Copy the code

UserServiceImpl.java

package com.xxxx.springsecurityoauth2demo.service.impl;

import com.xxxx.springsecurityoauth2demo.model.dao.UserMapper;
import com.xxxx.springsecurityoauth2demo.model.pojo.User;
import com.xxxx.springsecurityoauth2demo.model.req.ReqUser;
import com.xxxx.springsecurityoauth2demo.service.UserService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/** * UserServiceImpl */
@Service
public class UserServiceImpl implements UserService {
    @Resource
    private UserMapper userMapper;

    @Override
    public Object getAllUsers(a) {
        List<User> users = userMapper.selectAllUsers();
        return users;
    }

    @Override
    public User getUserByUserName(String username) {
        return userMapper.selectUserByUsername(username);
    }

    @Override
    public Object save(ReqUser reqUser) {
        int count = userMapper.save(reqUser);
        returncount; }}Copy the code

10. The Req object

User Request parameter object ReqUser requser.java

package com.xxxx.springsecurityoauth2demo.model.req;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/** * Description: User request parameter object */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ReqUser {
    private String account;

    private String description;

    private String password;

    private String name;
}
Copy the code

Postman test

(1) Password mode

After a user enters a password, the Client accesses a third party using the password. After the third party authenticates the user, the Client receives the Access Token. For example, the Reform movement requires wechat authentication. Everyone is in the same project, so it doesn’t matter if you know the password.

You need to access the fixed interface /oauth/token to enter fixed parameters

Once you get the token, you can use it to access the content of the resource server

(1) Access getCurrentUser from TestController to parse the token. (You can also go to the JWT official website or other websites for parsing, here I directly wrote the code parsing)

If the token is incorrect, authentication fails and access is prohibited

If the token expires, access is prohibited

Access the UserController.java interface to interact with the databaseAccess an interface that does not require authentication as long as it passes authentication

== Access another admin – only interface ==

The user is successfully added.

If a user with the permission of user accesses this interface, the access is denied after authentication.

(2) Authorization code mode

You need to access the specified address to obtain the authorization code and then obtain the token based on the authorization code The user-agent provides the client Credentials and redirect URIs to the authentication server. The authentication server then gives the Resource Owner the permission to authenticate. The User agrees. An authorization code is returned to the User-agent and then to the Client. After obtaining the authorization code, the Client will follow the URI and authorization code we redirected to find the authorization server again. In this case, the authentication server will return an Access Token (Refresh Toke can also be returned) based on the authorization code. The Access Token is required, and the Refresh Token is optional.

Here, the common user is used to access the API/SAVE interface that requires admin permission. First, the authorization code is obtained, and then the fixed URL is accessed to obtain the authorization code

Visit this link:

http://localhost:8080/oauth/authorize? response_type=code&client_id=client&redirect_uri=http://www.baidu.com&scope=all
Copy the code

The meaning of this link is http://localhost:8080 /oauth/authorize [fixed interface address] to our authorization server request, Response_type =code to obtain the authorization code &client_id=client Specifies the client ID &redirect_uri redirection address &scope=all

If you enter the above URL, the login page is automatically redirected. The reason is very simple. The authorization code mode does not carry the user name and password, so you need to log in.

Code = authorization code Then we can access resources by token

(3) Refresh_token mode

Since JWT tokens are stateless and will not be stored on the server side, we need to set Access tokens to be valid for a period of time that is not too long, otherwise security will be poor. (If you don’t set the validity period of the Access Token, there is no security.) But the Access Token expires too soon and the user needs to re-obtain the authorization code, i.e. re-enter the username and password to log in? Is that a bad user experience? Then there is the “refresh_token”. When the “refresh_token” expires, you only need to re-check to regain the “Access_Token”. Therefore, in general, the validity period of Access_Token is short, while the validity period of refresh_token is long.

Normal acquisition first

Assuming that the Access_token has expired, we only need to use the refresh_token to get a new Access_token without entering the username and password.

3. Integrate SSO

1. What is SSO (Single Sign On)?

Single sign-on (SSO for short) means that when you log in to one system in a multi-system application group, you can be authorized to log in to all other systems without having to log in again, including Single sign-on and Single sign-off

Speaking is when you log in baidu home page, the left red box baidu system under the bold style site do not need to log in, to achieve a one-time identification, the whole site login, very friendly to the user.

Compared with single-system login, sso requires an independent authentication center. Only the authentication center can accept security information such as user names and passwords. Other systems do not provide login portals and only accept indirect authorization from the authentication center. Indirect authorized by token, sso authentication center to verify the user’s user name password no problem, create the authorization token, in the process of the next jump, authorization token as parameters sent to each subsystem, subsystem get tokens, quick to authorize, to create a local session, local session login method with single system login the same way.

2. Project integration to achieve SSO

(1) Client engineering structure

application.properties

server.port=8081
# If multiple clients do not configure cookies, the Cookie name will be the same, and Cookie conflict will occur, which will lead to failure of login authentication
server.servlet.session.cookie.name=OAUTH2-CLIENT-SESSIONID01
# license server address
oauth2-server-url:http://localhost:8080
Configure the authorization server
security.oauth2.client.client-id=client
security.oauth2.client.client-secret=112233
Obtain the address configuration of the authorization code
security.oauth2.client.user-authorization-uri=${oauth2-server-url}/oauth/authorize
Get Access Token address configuration
security.oauth2.client.access-token-uri=${oauth2-server-url}/oauth/token
TokenStore = JwtTokenStore = / oAuth /token_key = /oauth/token_key = /oauth/token_key = /oauth/token_key
security.oauth2.resource.jwt.key-uri=${oauth2-server-url}/oauth/token_key


Copy the code

Controller

Controller.java (simple implementation, test)

package com.xxxx.oauth2client01demo.controller;

import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/** *
@RestController
@RequestMapping("/user")
public class Controller {

    @RequestMapping("/getCurrentUser")
    public Object getCurrentUser(Authentication authentication) {
        returnauthentication; }}Copy the code

SSO annotations are enabled in the project entry

(2) Authentication server engineering structure

In order to study and test, we will use the authentication server of this project and will not create the authentication server independently.

Specify the redirection address

Single sign-on configuration

3. The SSO test

Switch to the authentication server. After the authentication is complete, the system automatically switches back to the IP address of the interface to be accessed. In this case, the system can access the resources on the client.

4. Project source code

Project source code address

Making: github.com/pleineluna/…

Gitee:gitee.com/tsukuyo98/l…

SSO client source address:

Making: github.com/pleineluna/…

Gitee:gitee.com/tsukuyo98/o…

(Please click Star⭐ by the way)

References:

www.cnblogs.com/xiaofengxzz…

www.bilibili.com/video/BV1Cz…