Проект интеграции 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&characterEncoding=UTF-8&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…