Сверхдетальный проект интеграции Spring Security OAuth2 JWT (SSO)

Spring Boot задняя часть
Сверхдетальный проект интеграции Spring Security OAuth2 JWT (SSO)

Проект интеграции Spring Security OAuth2 JWT SSO

предисловие

Этот проект представляет собой проект интеграции Spring Security OAuth2 JWT SSO. Он подходит для всех, кто имеет определенное представление о Spring Security, OAuth2 и JWT SSO и хочет органично интегрироваться.Проект написан по принципу максимально простого старта и максимально полных базовых функций. Аутентификация и аутентификация на основе базы данных. При использовании аутентификации OAuth2 процесс аутентификации выполняется в соответствии с информацией о пользователе, запрашиваемой сервером аутентификации, а аутентификация выполняется на сервере ресурсов в соответствии с полномочиями. Здесь используется режим проверки подлинности разрешений, и диапазон серверов ресурсов, к которым может получить доступ шлюз, определяется в соответствии с разрешением пользователя. [Кроме того, есть аутентификация на основе роли, просто поменяйте суп, но не лекарство]

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

System.out.println("Исходный код лучше скачать напрямую! Исходный код приведен внизу статьи!");

}

Исходный код был подробно аннотирован и может быть понят вместе со статьей.

Давайте возьмем каштан, чтобы понять процесс аутентификации Oauth2, веб-сайт использует аутентификацию WeChat: (сверху вниз, следуйте направлению стрелки)在这里插入图片描述

1. Используйте шаги

1. Инженерное сооружение

在这里插入图片描述

2. Добавьте зависимости

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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-SNAPSHOT</version>
    <name>springsecurityoauth2-demo</name>
    <description>Demo project for Spring Boot</description>
    <!--设置spring cloud以及jdk版本变量-->
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
    </properties>
    <dependencies>
        <!--oauth2和security使用spring cloud里面的组件-->
        <!--只有引入了spring cloud依赖以及版本后才会生效-->
        <dependency>
            <!--oauth2依赖-->
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <!--security依赖-->
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>
        <dependency>
            <!--web依赖-->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <!--test依赖-->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
            <!--jwt 依赖-->
        <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-Spring-Boot-Starter类似一个中间件,链接Spring Boot和MyBatis,构建基于Spring Boot的MyBatis应用程序-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <!--    mysql-connector-java 是MySQL提供的JDBC驱动包,用JDBC连接MySQL数据库时必须使用该jar包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
    <!--dependencyManagement利用版本变量引入spring cloud依赖。-->
    <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是mybatis自动生成实体代码的插件-->
            <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>

3. Оператор создания таблицы базы данных

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, '管理员拥有所有接口操作权限', 1627199362, '管理员', 'ADMIN');
INSERT INTO `um_t_role` VALUES (2, '普通拥有查看用户列表与修改密码权限,不具备对用户增删改权限', 1627199362, '普通用户', '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', '系统默认管理员', '$2a$10$N97RyMYeQ7aVTxLvdxq5NeBivdbj/u2GQtHERISUt8qhKBfnjSC1q', 'admin');
INSERT INTO `um_t_user` VALUES (2, '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;

==Все пароли пользователей 123456==

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

4. Напишите соответствующие файлы конфигурации

(1) GeneratorConfig.xml MyBatis-Generator - это файл конфигурации

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
  PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
  "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
  <!-- generatorConfig.xml配置文件,放在resource目录下即可 -->
  <!--数据库驱动个人配置-->
  <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保留字冲突报错-->
    <property name="beginningDelimiter" value="`"/>
    <property name="endingDelimiter" value="`"/>
    <!-- optional,旨在创建class时,对注释进行控制 -->
    <commentGenerator>
      <property name="suppressDate" value="true"/>
      <property name="suppressAllComments" value="true"/>
    </commentGenerator>
    <!--数据库链接地址账号密码-->
    <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>
    <!-- 非必需,类型处理器,在数据库类型和java类型之间的转换控制-->
    <javaTypeResolver>
      <property name="forceBigDecimals" value="false"/>
    </javaTypeResolver>
    <!--生成Model类存放位置-->
    <javaModelGenerator targetPackage="com.xxxx.springsecurityoauth2demo.model.pojo"
      targetProject="src/main/java">
      <!-- 是否允许子包,即targetPackage.schemaName.tableName -->
      <property name="enableSubPackages" value="true"/>
      <!-- 是否对类CHAR类型的列的数据进行trim操作 -->
      <property name="trimStrings" value="true"/>
      <!-- 建立的Model对象是否 不可改变  即生成的Model对象不会有 setter方法,只有构造方法 -->
      <property name="immutable" value="false"/>
    </javaModelGenerator>
    <!--生成mapper映射文件存放位置-->
    <sqlMapGenerator targetPackage="mappers" targetProject="src/main/resources">
      <property name="enableSubPackages" value="true"/>
    </sqlMapGenerator>
    <!--生成Dao类存放位置-->
    <javaClientGenerator type="XMLMAPPER" targetPackage="com.xxxx.springsecurityoauth2demo.model.dao"
      targetProject="src/main/java">
      <property name="enableSubPackages" value="true"/>
    </javaClientGenerator>
    <!--生成对应表及类名 schema 指数据库的用户名-->
    <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>

(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

5. Используйте mybatis-generator для создания классов сущностей, Mapper и XML-документов.

在这里插入图片描述

6. Классы конфигурации Security, OAuth2, JWT, SSO

(1) Сервер авторизации

Используется для настройки авторизации. Необходимо наследовать класс AuthorizationServerConfigurerAdapter и переопределить метод configure().

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;

/**
 * 描述:授权服务器 @EnableAuthorizationServer,extends AuthorizationServerConfigurerAdapter
 * 为了模拟,授权服务器和资源服务器放在了一起,正常情况是解耦的。
 */
@Configuration
@EnableAuthorizationServer //开启授权服务器
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;

    /**
     * 密码授权模式的配置
     *
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //TokenEnhancerChain是TokenEnhance的一个实现类
        TokenEnhancerChain chain = new TokenEnhancerChain();
        List<TokenEnhancer> delegates = new ArrayList<>();
        delegates.add(jwtTokenEnhancer);
        delegates.add(jwtAccessTokenConverter);//还要把转换器放进去用来实现jwtTokenEnhancer的互相转换
        chain.setTokenEnhancers(delegates);
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService)
                //可以看到主要是增加了 JwtAccessTokenConverter JWT访问令牌转换器和JwtTokenStore JWT令牌存储组件,
                //通过AuthorizationServerEndpointsConfigurer 授权服务器端点配置加入两个实例
                .tokenStore(tokenStore)
                .accessTokenConverter(jwtAccessTokenConverter)
                .tokenEnhancer(chain); //设置JWT增强内容


    }

    /**
     * 授权配置
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        /*传来的参数clients是我们的应用,要去找授权服务器授权,授权完了之后会给我们授权码,我们
         * (client)拿着授权码再到授权服务器去获取令牌,获取到令牌之后拿着令牌去资源服务器获取资源
         * */

        clients.inMemory() //.inMemory()放入内存。我们为了方便,直接放在内存中生成client,正常情况下是我们主动找授权服务器注册的时候才会有处理。
                .withClient("client") //指定client。参数为唯一client的id
                .secret(passwordEncoder.encode("112233")) //指定密钥
                .redirectUris("http://www.baidu.com") //指定重定向的地址,通过重定向地址拿到授权码。
                //.redirectUris("http://localhost:8081/login") //单点登录到另一服务器
                .accessTokenValiditySeconds(60 * 10) //设置Access Token失效时间
                .refreshTokenValiditySeconds(60 * 60 * 24) //设置refresh token失效时间
                .scopes("all") //指定授权范围
                .autoApprove(true) //自动授权,不需要手动允许了
                /**
                 * 授权类型:
                 * "authorization_code" 授权码模式
                 * "password"密码模式
                 * "refresh_token" 刷新令牌
                 */
                .authorizedGrantTypes("authorization_code", "password", "refresh_token"); //指定授权类型 可以多种授权类型并存。

    }

    /**
     * 单点登录配置
     *
     * @param security
     * @throws Exception
     */
    /*@Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //必须要身份认证,单点登录必须要配置
        security.tokenKeyAccess("isAuthenticated()");
    } */
}
 

(2) Менеджер ресурсов

В производственной среде предприятия сервер авторизации и сервер ресурсов — это два отдельных сервера.Мы используем один проект модели для обучения, поэтому мы объединяем их.

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;

/**
 * 描述:资源管理器 @EnableResourceServer,extends ResourceServerConfigurerAdapter
 * 为了模拟,授权服务器和资源服务器放在了一起,正常情况是解耦的。
 */
@Configuration
@EnableResourceServer //开启资源服务器
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
         http.requestMatchers().antMatchers("/api/**").and()
                .authorizeRequests()//授权的请求
                //进行接口的鉴权处理
                .antMatchers("/api/user/save").hasAuthority("admin")
                //其余接口不做鉴权,只需要认证即可
                .anyRequest()
                .authenticated();

    }
}

(3) Усилитель контента JWT

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;

/**
 * 描述:配置JwtTokenEnhancer添加自定义信息 ,继承TokenEnhancer实现一个JWT内容增强器
 */
public class JwtTokenEnhancer implements TokenEnhancer {
    /**
     * JWT内容增强器
     * @param oAuth2AccessToken
     * @param oAuth2Authentication
     * @return
     */
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
        Map<String, Object> info = new HashMap();
        info.put("enhance", "增强的信息");
        //给的参数是oAuth2的AccessToken,实现类是DefaultOAuth2AccessToken,
        //里面有个setAdditionalInformation方法添加自定义信息(Map类型)
        ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(info);
        return oAuth2AccessToken;
    }
}

(4) Класс конфигурации TokenStore

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;

/**
 * 描述:TokenStore配置类。
 * TokenStore的实现类,有InMemoryTokenStore、JdbcTokenStore、JwtTokenStore、RedisTokenStore。
 * JwtAccessTokenConverter JWT访问令牌转换器和 JwtTokenStore JWT令牌存储组件
 */
@Configuration
public class JwtTokenStoreConfig {
    /**
     * 生成TokenStore来保存token  此处为JwtTokenStore实现
     * @return TokenStore
     */
    @Bean
    public TokenStore jwtTokenStore() {
        //需要传入JwtAccessTokenConverter
        return new JwtTokenStore(jwtAccessTokenConverter());
    }
    /**
     *  生成JwtAccessTokenConverter转换器,并设置密钥
     * @return JwtAccessTokenConverter
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        //设置jwt密钥
        jwtAccessTokenConverter.setSigningKey("test_key");
        return jwtAccessTokenConverter;
    }

    /**
     * JwtTokenEnhancer的注入
     * @return
     */
    @Bean
    public JwtTokenEnhancer jwtTokenEnhancer() {
        return new JwtTokenEnhancer();
    }
}

(5) Класс конфигурации ядра безопасности

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;

/**
 * 描述:Security核心配置类
 * 1.重写configure(HttpSecurity http)
 * 2.配置 PasswordEncoder的Ioc注入。
 */
@Configuration
@EnableWebSecurity //开启Web Security
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置没有权限访问跳转自定义页面
        http.exceptionHandling().accessDeniedPage("/unauth.html");

        http.authorizeRequests()
                //放行授权服务器的几个端点请求、登录请求、登出请求。
                .antMatchers("/oauth/**", "/login/**", "/logout/**")
                .permitAll()
                .anyRequest()
                .authenticated()
                //.and() 就相当于回到 http再继续配置
                .and()
                //放行所有的表单请求
                .formLogin()
                .permitAll()
                .and()
                //关闭csrf
                .csrf().disable();

    }

    /**
     * 密码授权模式用到的AuthenticationManager类
     *
     * @return
     * @throws Exception
     */
    @Override
    @Bean
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }


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

7. Настройте UserDetailService Spring Security для запроса информации из базы данных для аутентификации.

(1) Интерфейс MyUserService

package com.xxxx.springsecurityoauth2demo.service;
/**
 * 描述:MyUserService接口
 */
public interface MyUserService{
}

(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;

/**
 * 描述:自定义UserDetailsService实现类
 * 名言:越难找的bug往往是越低级的
 */
@Service  //因为没有加Service注解,所以please login  一直报用户名密码错误!!!
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 new SecurityUser(name, password, AuthorityUtils.commaSeparatedStringToAuthorityList(authority));
    }
}

(3) Пользовательский объект пользовательской инфраструктуры безопасности

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;

/**
 * 描述:自定义Security框架的User实体
 */
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() {
        return password;
    }

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

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

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

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

    @Override
    public boolean isEnabled() {
        return true;
    }
}

8. Уровень контроллера

(1) Контроллер для тестирования токена jwt

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;

/**
 * 描述:UserController  模拟资源服务器用的,用来访问资源的。
 */
@RestController
@RequestMapping("/user")
public class TestUserController {

    //测试用的,不与数据库做连接
    @RequestMapping("/getCurrentUser")
    public Object getCurrentUser(Authentication authentication, HttpServletRequest request) {
        //Authorization是在请求头中的属性。
        String header = request.getHeader("Authorization");
        //bearer :jwt token,所以bearer加空格后的第七个才是token。
        String token = header.substring(header.lastIndexOf("bearer") + 7);
        return Jwts.parser()    
                .setSigningKey("test_key".getBytes(StandardCharsets.UTF_8))//指定编码格式,要不然token有中文转换异常
                .parseClaimsJws(token)
                .getBody();
    }


}

(2) Сервер пользовательских ресурсов 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;

/**
 * 描述:User资源服务器 UserController
 */
@RestController
@RequestMapping("/api/user")
public class UserController {
    @Resource
    private UserService userService;

    /**
     * 得到所有用户列表,所有权限可以访问
     * @return
     */
    @PostMapping("/users")
    public Object getAllUsers() {
        return userService.getAllUsers();
    }

    /**
     * 增加用户,只有权限为admin的用户才可以访问
     */
    @PostMapping("/save")
    public Object save(@RequestBody ReqUser reqUser) {
        return userService.save(reqUser);
    }
}

9.Служебный слой

(1) Интерфейс UserService

UserService.java

package com.xxxx.springsecurityoauth2demo.service;

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

/**
 * 描述:UserService接口
 */
public interface UserService {
    Object getAllUsers();

    User getUserByUserName(String username);

    Object save(ReqUser reqUser);
}

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() {
        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);
        return count;
    }


}

10. Требуемый объект

Объект параметра запроса пользователя ReqUser ReqUser.java

package com.xxxx.springsecurityoauth2demo.model.req;

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

/**
 * 描述:User请求参数对象
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ReqUser {
    private String account;

    private String description;

    private String password;

    private String name;
}

Два, тест почтальона

(1) Режим пароля

在这里插入图片描述

Если пользователь напрямую вводит пароль, клиент будет использовать имя пользователя и пароль для прямого доступа к третьей стороне, и третья сторона вернет токен доступа после аутентификации. В основном используется для разных портов одного и того же проекта, например, Reform Movement требует аутентификации WeChat, все находятся в одном проекте, и не имеет значения, знаете ли вы пароль.

Необходимо получить доступ к фиксированному интерфейсу /oauth/token для ввода фиксированных параметров 在这里插入图片描述 在这里插入图片描述

После получения токена вы можете получить доступ к содержимому сервера ресурсов с токеном

(1) Сначала посетите getCurrentUser в TestController, чтобы проанализировать токен. (Вы также можете перейти на официальный сайт JWT или другие сайты для анализа, здесь анализ кода я писал непосредственно сам)在这里插入图片描述

Если токен неверный, аутентификация не проходит и доступ запрещается 在这里插入图片描述

Если срок действия токена истек, отключите доступ

在这里插入图片描述

Получите доступ к интерфейсу UserController.java для взаимодействия с базой данных.Сначала получите доступ к интерфейсу, который не требует аутентификации и может быть доступен до тех пор, пока аутентификация пройдена.在这里插入图片描述

==Затем получите доступ к интерфейсу, который разрешает доступ только администратору.==

Пользователь успешно добавлен.

在这里插入图片描述

Заменить разрешение на доступ к этому интерфейсу для пользователей, после аутентификации отказывающихся авторизовать доступ. 在这里插入图片描述 在这里插入图片描述

(2) Режим кода авторизации

Вам необходимо получить доступ к указанному адресу, чтобы сначала получить код авторизации, а затем получить токен в соответствии с кодом авторизации 在这里插入图片描述User-Agent эквивалентен странице входа в систему, которая появляется при входе в систему через третье лицо) будет приносить учетные данные клиента (учетные данные клиента) и перенаправлять uri на сервер для аутентификации.Сервер аутентификации позволит владельцу ресурса, который является пользователю для аутентификации.Если вы согласны, код авторизации будет возвращен Агенту пользователя, а затем Клиенту. После того, как клиент-клиент получит код авторизации, он будет следовать URI и коду авторизации, которые мы перенаправили, чтобы снова найти сервер авторизации.В это время сервер аутентификации вернет токен доступа (или токен обновления) в соответствии с кодом авторизации. Требуется токен доступа, а токен обновления необязателен.

Здесь обычный пользователь используется для доступа к интерфейсу API/сохранения, для которого требуются права администратора. Сначала получите код авторизации, перейдите по фиксированному URL-адресу, чтобы получить код авторизации.

Посетите эту ссылку:

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

Значение этой ссылкиhttp://локальный:8080/oauth/authorize [фиксированный адрес интерфейса] Запрос к нашему серверу авторизации, response_type=code для получения кода авторизации &client_id=client указывает id клиента [id клиента, определенный вами] &redirect_uri [тот, которого вы хотите перенаправить] перенаправление address &scope =all — это полный диапазон.

Ввод вышеуказанного URL-адреса автоматически перейдет на страницу входа.Причина очень проста.Режим кода авторизации не содержит имя пользователя и пароль, поэтому вам необходимо войти в систему.Это также одно из отличий режима кода авторизации и режим пароля.

在这里插入图片描述

код=код авторизации 在这里插入图片描述 在这里插入图片描述Далее мы можем использовать токен для доступа к ресурсам.

(3) режим refresh_token

Поскольку токен JWT не имеет состояния и не будет храниться на стороне сервера, нам необходимо установить время действия токена доступа, и это время не может быть слишком большим, иначе безопасность будет плохой. (Если не установить время действия Access Token, то безопасности нет вообще) Но срок действия Access Token истекает слишком быстро и пользователю необходимо заново получить код авторизации, то есть повторно ввести логин и пароль чтобы залогиниться? Так что пользовательский опыт слишком плохой, верно? Так что есть refresh_token, Когда срок действия Access_Token истекает, вам нужно только снова проверить refresh_token, чтобы снова получить Access_Token. Таким образом, срок действия Access_Token будет очень коротким, а срок действия refresh_token будет больше.

Получите это нормально

在这里插入图片描述 Предполагая, что срок действия Access_token истек, нам нужно использовать refresh_token только для получения нового Access_Token без ввода таких параметров, как имя пользователя и пароль.在这里插入图片描述 在这里插入图片描述

3. Интегрируйте систему единого входа

1. Что такое SSO (единый вход)

Полное название единого входа — Single Sign On (далее — SSO).

在这里插入图片描述

Говоря человеческими словами, когда вы входите на домашнюю страницу Baidu, сайты, выделенные жирным шрифтом в системе Baidu в красном поле слева, не нуждаются в входе в систему, что реализует однократную идентификацию и полный вход на сайт, что чрезвычайно удобен для пользователя.

在这里插入图片描述По сравнению с односистемным входом в систему, sso требует независимого центра аутентификации. Только центр аутентификации может принимать информацию о безопасности, такую ​​как имя пользователя и пароль. Другие системы не предоставляют вход в систему и принимают только косвенную авторизацию из центра аутентификации. Косвенная авторизация достигается с помощью токенов. Центр аутентификации sso без проблем проверяет имя пользователя и пароль пользователя, создает токен авторизации. В процессе следующего перехода токен авторизации отправляется в качестве параметра каждой подсистеме, и подсистема получает токен. , то есть авторизованный, вы можете использовать его для создания локального сеанса, а метод входа в локальный сеанс такой же, как и в одиночной системе.

2. Интеграция проекта для достижения SSO

(1) Инженерная структура клиента

在这里插入图片描述

application.properties

server.port=8081
#多客户端不配置Cookie的话会导致cookie的name相同,会出现Cookie冲突,冲突会导致登录验证不通过
server.servlet.session.cookie.name=OAUTH2-CLIENT-SESSIONID01
#授权服务器地址
oauth2-server-url:http://localhost:8080
#与授权服务器对应的配置
security.oauth2.client.client-id=client
security.oauth2.client.client-secret=112233
#获取授权码的地址配置
security.oauth2.client.user-authorization-uri=${oauth2-server-url}/oauth/authorize
#获取Access Token的地址配置
security.oauth2.client.access-token-uri=${oauth2-server-url}/oauth/token
#获取Jwt Token (授权服务器基于Spring Cloud Oauth2创建后,配置TokenStore为JwtTokenStore,访问/oauth/token_key接口获取公钥)
security.oauth2.resource.jwt.key-uri=${oauth2-server-url}/oauth/token_key


Controller

Controller.java (простая реализация, для тестирования)

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;

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

    @RequestMapping("/getCurrentUser")
    public Object getCurrentUser(Authentication authentication) {
        return authentication;
    }
}

Включить аннотацию SSO при входе в проект

在这里插入图片描述

(2) Инженерная структура аутентификации на стороне сервера

Для обучения и тестирования мы продолжаем использовать сервер аутентификации этого проекта и больше не создаем отдельный сервер аутентификации.

Укажите адрес перенаправления

在这里插入图片描述

Конфигурация единого входа

在这里插入图片描述

3. Тест системы единого входа

在这里插入图片描述

После перехода на сервер аутентификации для завершения аутентификации он автоматически возвращается к адресу интерфейса, к которому необходимо получить доступ, и в это время можно получить доступ к ресурсам клиента. 在这里插入图片描述

4. Исходный код проекта

Адрес исходного кода проекта

GitHub: GitHub.com/категория Луна/…

Гостиница:git ee.com/он жалуется лет 98/приходите…

Исходный адрес клиента системы единого входа:

GitHub: GitHub.com/категория Луна/…

Гостиница:git ee.com/ он жалуется лет 98/ ой…

(Кстати, очень прошу заказать Стар⭐)

Использованная литература:

Блог Wooooooo.cn on.com/In the small gap…

воооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооо по