springcloud-sso单点登陆(一)
单点登陆的实现方式有多种:
1、基于cas来做
2、spring cloud oauth2的spring全家桶
3、自定义jwt(不推荐)
下面我们基于spring全家桶来做一个单点登陆系统2019年12月23日
最新springboot版本2.2.2.RELEASE
;由于篇幅问题,我们分成两篇文章。
下一篇:https://www.jianshu.com/p/074653aaa405
源码:https://github.com/xcocean/spring-cloud-sso
一、搭建统一依赖管理
项目maven父子项目结构:
parent(root)
|__dependencies
|__oauth2-server
|__business-user
1、创建一个文件夹spring-cloud-sso
用idea打开,然后new 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 http://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.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.qbccn</groupId>
<artifactId>parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<url>http://www.qbccn.com</url>
<modules>
<module>dependencies</module>
</modules>
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.qbccn</groupId>
<artifactId>dependencies</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<profiles>
<profile>
<id>default</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<spring-javaformat.version>0.0.12</spring-javaformat.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>io.spring.javaformat</groupId>
<artifactId>spring-javaformat-maven-plugin</artifactId>
<version>${spring-javaformat.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<includes>
<include>**/*Tests.java</include>
</includes>
<excludes>
<exclude>**/Abstract*.java</exclude>
</excludes>
<systemPropertyVariables>
<java.security.egd>file:/dev/./urandom</java.security.egd>
<java.awt.headless>true</java.awt.headless>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<executions>
<execution>
<id>enforce-rules</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<bannedDependencies>
<excludes>
<exclude>commons-logging:*:*</exclude>
</excludes>
<searchTransitive>true</searchTransitive>
</bannedDependencies>
</rules>
<fail>true</fail>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
<inherited>true</inherited>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<repositories>
<repository>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestone</id>
<name>Spring Milestone</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshot</id>
<name>Spring Snapshot</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestone</id>
<name>Spring Milestone</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-snapshot</id>
<name>Spring Snapshot</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>
然后在项目下创建一个文件夹dependencies
,在dependencies
下创建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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.qbccn</groupId>
<artifactId>dependencies</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<url>http://www.qbccn.com</url>
<properties>
<!--spring cloud-->
<spring-cloud-starter-oauth2>2.1.4.RELEASE</spring-cloud-starter-oauth2>
<!--mysql-->
<HikariCP>3.2.0</HikariCP>
<mybatis-spring-boot-starter>2.1.1</mybatis-spring-boot-starter>
<mysql-connector-java>8.0.18</mysql-connector-java>
<!--http-->
<okhttp>3.14.2</okhttp>
<!--other-->
<lombok>1.18.10</lombok>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok}</version>
<optional>true</optional>
</dependency>
<!-- oauth2 server start-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>${spring-cloud-starter-oauth2}</version>
</dependency>
<!-- oauth2 server end-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot-starter}</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>${HikariCP}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<!-- 排除 tomcat-jdbc 以使用 HikariCP -->
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp}</version>
</dependency>
</dependencies>
</dependencyManagement>
<repositories>
<repository>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestone</id>
<name>Spring Milestone</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshot</id>
<name>Spring Snapshot</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestone</id>
<name>Spring Milestone</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-snapshot</id>
<name>Spring Snapshot</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>
二、创建认证授权SSO:oauth2-server
在spring-cloud-sso
下创建文件夹oauth2-server
,在oauth2-server
下创建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>com.qbccn</groupId>
<artifactId>parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.qbccn</groupId>
<artifactId>oauth2-server</artifactId>
<description>认证授权中心</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- oauth2 server start-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!-- oauth2 server end-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<!-- 排除 tomcat-jdbc 以使用 HikariCP -->
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.qbccn.Oauth2ServerApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
在父pom.xml
加上<module>oauth2-server</module>
然后关闭IDEA,重新打开让idea识别maven项目,更新项目。
然后手动创建src/main/java
和src/main/resources
;在java下创建com.qbccn.Oauth2ServerApplication.java
基本的springboot项目:
@SpringBootApplication
public class Oauth2ServerApplication {
public static void main(String[] args) {
SpringApplication.run(Oauth2ServerApplication.class, args);
}
}
关键配置如下
1、WebSecurityConfiguration
package com.qbccn.config;
import com.qbccn.config.impl.UserDetailsServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Bean
public BCryptPasswordEncoder passwordEncoder() {
// 设置默认的加密方式
return new BCryptPasswordEncoder();
}
/**
* 用于支持 password 模式
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
@Override
public UserDetailsService userDetailsService() {
return new UserDetailsServiceImpl();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 使用自定义认证与授权
auth.userDetailsService(userDetailsService());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//关闭跨域认证
http.csrf().disable();
http.exceptionHandling()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
2、AuthorizationServerConfiguration
package com.qbccn.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
/**
* 注入用于支持 password 模式
*/
@Autowired
private AuthenticationManager authenticationManager;
@Bean
public TokenStore tokenStore() {
// 基于 redis 实现,令牌保存到redis,也可以存到数据库中
return new RedisTokenStore(redisConnectionFactory);
}
@Bean
@Primary
public DefaultTokenServices defaultTokenServices() {
DefaultTokenServices services = new DefaultTokenServices();
services.setAccessTokenValiditySeconds(60 * 60 * 12);//设置token 20秒过期
services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 30);//设置刷新token的过期时间
services.setTokenStore(tokenStore());
return services;
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
// 用于支持密码模式
.authenticationManager(authenticationManager)
.tokenStore(tokenStore());
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
// 允许客户端访问 /oauth/check_token 检查 token
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients();
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 配置客户端,一般是读取数据库的,因为可能会变动,这里我们写死放在内存中即可
clients
// 使用内存设置
.inMemory()
// client_id
.withClient("client")
// client_secret
.secret(passwordEncoder.encode("secret"))
// 授权类型
.authorizedGrantTypes("password", "refresh_token")
//资源Id
.resourceIds("backend-resources")
// 授权范围
.scopes("backend");
}
}
3、UserDetailsServiceImpl
package com.qbccn.config.impl;
import com.qbccn.entity.SsoRole;
import com.qbccn.entity.SsoUser;
import com.qbccn.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 查询用户信息
SsoUser user = userService.getUserInfo(username);
if (user == null) {
throw new UsernameNotFoundException("帐号:" + username + " 不存在!");
}
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
//查询拥有的角色
List<SsoRole> roles = userService.getRoleByUserId(user.getId());
roles.forEach(r -> {
//security的验证规则需要ROLE_ 对应数据库的角色名称也要以ROLE_XXXX这种形式
grantedAuthorities.add(new SimpleGrantedAuthority(r.getRoleName()));
});
//spring5+要求密码加密,这里传明文密码,所以再加密
String password = new BCryptPasswordEncoder().encode(user.getPassword());
return new User(user.getUsername(), password, grantedAuthorities);
}
}
application.yml:
spring:
main:
allow-bean-definition-overriding: true
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/sso?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456
hikari:
minimum-idle: 1
idle-timeout: 600000
maximum-pool-size: 10
auto-commit: true
pool-name: MyHikariCP
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1
redis:
port: 6379
host: 127.0.0.1
jedis:
pool:
min-idle: 1
mybatis:
mapper-location: classpath:mapper/*.xml
SQL:
DROP TABLE IF EXISTS `sso_role`;
CREATE TABLE `sso_role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`role_name` varchar(30) DEFAULT NULL,
`role_desc` varchar(50) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
INSERT INTO `sso_role` VALUES ('1', '1', 'ROLE_USER', '用户角色', '2019-12-23 22:28:45', null);
INSERT INTO `sso_role` VALUES ('2', '2', 'ROLE_USER', '用户角色', '2019-12-23 22:28:45', null);
INSERT INTO `sso_role` VALUES ('3', '2', 'ROLE_ADMIN', '管理员', '2019-12-23 22:28:45', null);
DROP TABLE IF EXISTS `sso_user`;
CREATE TABLE `sso_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`username` varchar(20) DEFAULT NULL,
`password` varchar(20) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
INSERT INTO `sso_user` VALUES ('1', '凌康', 'lk', '123', '2019-12-23 22:28:11', null);
INSERT INTO `sso_user` VALUES ('2', 'admin', 'admin', '123', '2019-12-23 22:28:23', null);
运行项目用postman测试:
image.png
项目结构如下
image.png