单点登录,英文是 Single Sign On(缩写为 SSO)。就是多个站点公用一台认证服务器,比如下图我刚回答了个问题,在写一篇博客,是不需要再次登录的;而且各站点可以通过该登录状态实现交互。
**注意:登录是一个独立的系统如下图:**不管在系统1还是系统2登录,他们都会去调用认证授权,其目的就是为了保护数据安全性,判断用户的合法性!
- 单点登陆系统解决方案设计
本次项目中用到的技术有①JWT ②SpringSecurity安全框架 ③OAuth2
JWT解释
注:JWT中不会存储用户密码,一般存储权限等
- 创建父工程sso 修改pom和配置文件并且在父工程中定义版本
1.2在创建一个子工程sso-system继承父工程4.0.0 com.jt 02-sso pom 1.0-SNAPSHOT sso-system org.springframework.boot spring-boot-dependencies 2.3.2.RELEASE pom import org.springframework.cloud spring-cloud-dependencies Hoxton.SR9 pom import com.alibaba.cloud spring-cloud-alibaba-dependencies 2.2.6.RELEASE pom import org.projectlombok lombok provided org.springframework.boot spring-boot-starter-test test org.junit.vintage junit-vintage-engine org.apache.maven.plugins maven-compiler-plugin 3.8.1 8 8
02-sso com.jt 1.0-SNAPSHOT 4.0.0 sso-system mysql mysql-connector-java com.baomidou mybatis-plus-boot-starter 3.4.2 com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config org.springframework.boot spring-boot-starter-web
编写配置文件bootstrap.yml
server:
port: 8061
spring:
application:
name: sso-system
cloud:
nacos:
config:
server-addr: localhost:8848
file-extension: yml
discovery:
server-addr: localhost:8848
datasource:
url: jdbc:mysql:///jt-sso?serverTimezone=Asia/Shanghai&characterEncoding=utf8
username: root
password: root
logging:
level:
com.jt: debug
然后测试一下数据控连接
创建实体类User
创建mapper实现基于用户名称查询用户信息和基于用户id查询用户权限
统一认证工程的设计及实现
目的:用户登录时,调用此工程对用户身份进行核验,并授权
pom依赖
02-sso com.jt 1.0-SNAPSHOT 4.0.0 sso-auth org.springframework.boot spring-boot-starter-web com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.cloud spring-cloud-starter-openfeign org.springframework.cloud spring-cloud-starter-oauth2
配置文件
server:
port: 8071
spring:
application:
name: sso-auth
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848
file-extension: yml
启动类
启动类启动成功后会在控制台生成一个秘钥,然后访问端口号进去一个SpringSecurity进入一个页面,user为底层创建的用户,秘钥控制台生成,登录以后404则代表登录成功,因为没有要跳转的页面,会出现404异常
定义用户信息处理对象
定义User:用于封装sso-system工程去查询到的用户信息
定义远程service对象,用于实现远程用户信息调用
定义用户登陆业务逻辑处理对象
package com.jt.auth.service.impl;
import com.jt.auth.service.RemoteUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
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.stereotype.Service;
import java.util.List;
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private RemoteUserService remoteUserService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//1.基于用户名获取远端(现在这里指system服务)用户信息
com.jt.auth.pojo.User user = remoteUserService.listUserByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
//2.基于远端用户id获取用户权限id
List userPermissions =
remoteUserService.listUserByUserIdPermissions(user.getId());
//3.封装用户信息并返回,交给认证管理器(AuthenticationManager)对用户身份进行认证
return new User(username,
user.getPassword(),
AuthorityUtils.createAuthorityList(
userPermissions.toArray(new String[]{})));
//AuthenticationManager这个接口底层已经提供好认证方法,这里只用给他提供数据即可
}
}
定义Security配置类
package com.jt.auth.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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
定义Oauth2认证授权配置
package com.jt.auth.config;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
@AllArgsConstructor
@Configuration
@EnableAuthorizationServer
public class Oauth2Config extends AuthorizationServerConfigurerAdapter {
private BCryptPasswordEncoder passwordEncoder;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()//把规则写到内存里
.withClient("gateway-client")//定义客户端的标识(客户端到认证服务地址去认证时,需要携带这个信息)
.secret(passwordEncoder.encode("123456"))//定义客户端携带的秘钥
.authorizedGrantTypes("password", "refresh_token")//定义授权的类型,password基于密码进行认证,refresh_token基于刷新令牌认证
.scopes("all");//满足以上条件的客户端都可以认证
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()")//公开,定义这个认证地址(/oauth/token)
// 用户携带资料到这个地址认证,permitAll()允许所有的到这里认证
.checkTokenAccess("permitAll()")//公开校验token的地址(/oauth/check_token)
.allowFormAuthenticationForClients();//允许form表单的认证方式
}
private AuthenticationManager authenticationManager;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)//指定这个认证管理器对象完成认证
.tokenServices(tokenServices());//定义令牌服务(有默认令牌服务,但默认令牌服务不满足我们的需求)
}
private TokenStore tokenStore;
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Bean
public AuthorizationServerTokenServices tokenServices() {
//1.构建令牌服务对象
DefaultTokenServices tokenServices = new DefaultTokenServices();
//2.设置令牌创建及存储
tokenServices.setTokenStore(tokenStore);
//3.设置令牌增强器(默认是uuid格式的令牌)
tokenServices.setTokenEnhancer(jwtAccessTokenConverter);
//4.设置刷新令牌(服务端要创建一个刷新令牌)
tokenServices.setSupportRefreshToken(true);
//5.设置访问令牌,设置令牌的有效期
tokenServices.setAccessTokenValiditySeconds(3600);
//6.设置刷新令牌
tokenServices.setRefreshTokenValiditySeconds(5400);
return tokenServices;
}
}
但是认证完后需要颁发令牌,默认的不满足我们要求,需要我们手动配置,此类是给上边Oauth2认证授权配置服务的
1.4资源服务工程sso-resourcepom依赖
02-sso com.jt 1.0-SNAPSHOT 4.0.0 sso-resource org.springframework.boot spring-boot-starter-web com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config com.alibaba.cloud spring-cloud-starter-alibaba-sentinel org.springframework.cloud spring-cloud-starter-openfeign org.springframework.cloud spring-cloud-starter-oauth2
配置类
server:
port: 8881
spring:
application:
name: sso-resource
cloud:
nacos:
config:
server-addr: localhost:8848
file-extension: yml
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8180
启动类
配置类
controller
package com.jt.resource.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/resource")
public class ResourceController {
@PreAuthorize("hasAuthority('sys:res:list')")
@GetMapping
public String doSelect() {
return "select resource ok";
}
@PreAuthorize("hasAuthority('sys:res:create')")
@PostMapping
public String doCreate() {
return "create resource ok";
}
@PreAuthorize("hasAuthority('sys:res:update')")
@PutMapping
public String doUpdate() {
return "update resource ok";
}
@PreAuthorize("hasAuthority('sys:res:delete')")
@DeleteMapping
public String doDelete() {
return "delete resource ok";
}
@GetMapping("/export")
public String doExport() {
return "export resource ok";
}
}
定义一个匿名就可以访问资源的controller
package com.jt.resource.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/resource")
public class ResourceController {
@PreAuthorize("hasAuthority('sys:res:list')")
@GetMapping
public String doSelect() {
return "select resource ok";
}
@PreAuthorize("hasAuthority('sys:res:create')")
@PostMapping
public String doCreate() {
return "create resource ok";
}
@PreAuthorize("hasAuthority('sys:res:update')")
@PutMapping
public String doUpdate() {
return "update resource ok";
}
@PreAuthorize("hasAuthority('sys:res:delete')")
@DeleteMapping
public String doDelete() {
return "delete resource ok";
}
@GetMapping("/export")
public String doExport() {
return "export resource ok";
}
}
测试



