单点登陆的实现方式有多种:
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
:
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.2.2.RELEASE
com.qbccn
parent
0.0.1-SNAPSHOT
pom
http://www.qbccn.com
dependencies
1.8
${java.version}
${java.version}
UTF-8
UTF-8
com.qbccn
dependencies
${project.version}
pom
import
default
true
0.0.12
io.spring.javaformat
spring-javaformat-maven-plugin
${spring-javaformat.version}
org.apache.maven.plugins
maven-surefire-plugin
**/*Tests.java
**/Abstract*.java
file:/dev/./urandom
true
org.apache.maven.plugins
maven-enforcer-plugin
enforce-rules
enforce
commons-logging:*:*
true
true
org.apache.maven.plugins
maven-install-plugin
true
org.apache.maven.plugins
maven-javadoc-plugin
true
true
alimaven
aliyun maven
http://maven.aliyun.com/nexus/content/groups/public/
false
spring-milestone
Spring Milestone
https://repo.spring.io/milestone
false
spring-snapshot
Spring Snapshot
https://repo.spring.io/snapshot
true
spring-milestone
Spring Milestone
https://repo.spring.io/milestone
false
spring-snapshot
Spring Snapshot
https://repo.spring.io/snapshot
true
然后在项目下创建一个文件夹dependencies
,在dependencies
下创建pom.xml
:
4.0.0
com.qbccn
dependencies
0.0.1-SNAPSHOT
pom
http://www.qbccn.com
2.1.4.RELEASE
3.2.0
2.1.1
8.0.18
3.14.2
1.18.10
org.projectlombok
lombok
${lombok}
true
org.springframework.cloud
spring-cloud-starter-oauth2
${spring-cloud-starter-oauth2}
org.mybatis.spring.boot
mybatis-spring-boot-starter
${mybatis-spring-boot-starter}
com.zaxxer
HikariCP
${HikariCP}
org.springframework.boot
spring-boot-starter-jdbc
org.apache.tomcat
tomcat-jdbc
mysql
mysql-connector-java
${mysql-connector-java}
com.squareup.okhttp3
okhttp
${okhttp}
alimaven
aliyun maven
http://maven.aliyun.com/nexus/content/groups/public/
false
spring-milestone
Spring Milestone
https://repo.spring.io/milestone
false
spring-snapshot
Spring Snapshot
https://repo.spring.io/snapshot
true
spring-milestone
Spring Milestone
https://repo.spring.io/milestone
false
spring-snapshot
Spring Snapshot
https://repo.spring.io/snapshot
true
二、创建认证授权SSO:oauth2-server
在spring-cloud-sso
下创建文件夹oauth2-server
,在oauth2-server
下创建pom.xml
:
4.0.0
com.qbccn
parent
0.0.1-SNAPSHOT
com.qbccn
oauth2-server
认证授权中心
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.springframework.cloud
spring-cloud-starter-oauth2
org.springframework.boot
spring-boot-starter-data-redis
org.mybatis.spring.boot
mybatis-spring-boot-starter
com.zaxxer
HikariCP
org.springframework.boot
spring-boot-starter-jdbc
org.apache.tomcat
tomcat-jdbc
mysql
mysql-connector-java
com.squareup.okhttp3
okhttp
org.springframework.boot
spring-boot-maven-plugin
com.qbccn.Oauth2ServerApplication
在父pom.xml
加上
然后关闭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 grantedAuthorities = new ArrayList<>();
//查询拥有的角色
List 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测试:
项目结构如下