127.0.0.1 auth.server.com
127.0.0.1 user.server.com
127.0.0.1 third.server.com
127.0.0.1 eureka.server.com
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<version>3.1.1</version>
</dependency>
<!--web專案驅動-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot-start-version}</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.5.0</version>
</dependency>
/**
* @description: Eureka 伺服器端註冊中心:剔除資料來源操作
* @author: GuoTong
* @createTime: 2023-06-26 21:50
* @since JDK 1.8 OR 11
**/
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
/**
* @description: SpringBoot-Web設定
* @author: GuoTong
* @createTime: 2023-06-05 15:37
* @since JDK 1.8 OR 11
**/
@Configuration
public class SpringBootConfig implements WebMvcConfigurer {
/**
* Description: 新增全域性跨域CORS處理
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
// 設定允許跨域的路徑
registry.addMapping("/**")
//設定允許跨域請求的域名
.allowedOriginPatterns("*")
// 是否允許證書
.allowCredentials(true)
// 設定允許的方法
.allowedMethods("GET", "POST", "DELETE", "PUT")
// 設定允許的header屬性
.allowedHeaders("*")
// 跨域允許時間
.maxAge(3600);
}
/**
* Description: 靜態資源過濾
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//ClassPath:/Static/** 靜態資源釋放
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
//釋放swagger
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
//釋放webjars
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
server:
port: 10086
spring:
application:
name: eureka-server
security:
user:
name: eureka
password: eureka
mvc:
static-path-pattern: classpath:/static/**
eureka:
client:
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
register-with-eureka: false #自己不向註冊中心註冊自己
fetch-registry: false # 自己是註冊中心
instance:
hostname: 127.0.0.1
prefer-ip-address: true
https://sa-token.cc/doc.html 可以自己參考官方網站客製化
knife4j / mysql / mybatis / sa-token / commons / thymeleaf / loadbalancer / bootstrap /eureka-client / lombok
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--Knife4j(增強Swagger)-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<!--Mysql資料庫-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-version}</version>
</dependency>
<!--Mybatis-plus 程式碼生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatisplus.verison}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>${mybatisplus.verison}</version>
</dependency>
<!-- Sa-Token 許可權認證,線上檔案:https://sa-token.cc -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.33.0</version>
</dependency>
<!-- Sa-Token 外掛:整合SSO -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-sso</artifactId>
<version>1.33.0</version>
</dependency>
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>1.33.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- 檢視引擎(在前後端不分離模式下提供檢視支援) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--新版的移除了Ribbon的負載策略,所需改用新版的loadbalancer-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<version>${spring-cloud-starter-version}</version>
</dependency>
<!-- bootstrap最高階啟動設定讀取 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
<version>${spring-cloud-starter-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>3.1.1</version>
</dependency>
/**
* @description: 統一認證中心 SSO-Server用於對外開放介面:
* @author: GuoTong
* @createTime: 2022-11-26 23:03
* @since JDK 1.8 OR 11
**/
@RestController
public class SsoServerController {
private AuthLoginUserService authLoginUserService;
/**
* Description: 構造器注入
*/
public SsoServerController(AuthLoginUserService authLoginUserService) {
this.authLoginUserService = authLoginUserService;
}
/*
* /*
* SSO-Server端:處理所有SSO相關請求
* http://{host}:{port}/sso/auth -- 單點登入授權地址,接受引數:redirect=授權重定向地址
* http://{host}:{port}/sso/doLogin -- 賬號密碼登入介面,接受引數:name、pwd
* http://{host}:{port}/sso/checkTicket -- Ticket校驗介面(isHttp=true時開啟),接受引數:ticket=ticket碼、ssoLogoutCall=單點登出回撥地址 [可選]
* http://{host}:{port}/sso/signout -- 單點登出地址(isSlo=true時開啟),接受引數:loginId=賬號id、secretkey=介面呼叫祕鑰
*/
@RequestMapping("/sso/*")
public Object ssoRequest() {
return SaSsoProcessor.instance.serverDister();
}
/**
* 設定SSO相關引數
*/
@Autowired
private void configSso(SaSsoConfig sso) {
// 設定:未登入時返回的View
sso.setNotLoginView(() -> new ModelAndView("sa-login.html"));
// 設定:登入處理常式
sso.setDoLoginHandle((name, pwd) -> {
QueryWrapper<AuthLoginUser> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", name);
queryWrapper.eq("password", pwd);
AuthLoginUser user = authLoginUserService.getOne(queryWrapper);
if (user != null) {
StpUtil.login(user.getId());
return SaResult.ok("登入成功!").setData(StpUtil.getTokenValue());
}
return SaResult.error("登入失敗!");
});
}
}
/**
* 前後臺分離架構下整合SSO所需的程式碼 (SSO-Server端)
* <p>(注:如果不需要前後端分離架構下整合SSO,可刪除此包下所有程式碼)</p>
*
* @author kong
*/
@RestController
public class H5Controller {
@Autowired
private AuthLoginUserService authLoginUserService;
/**
* 獲取 redirectUrl
*/
@RequestMapping("/sso/getRedirectUrl")
private Object getRedirectUrl(String redirect, String mode) {
// 未登入情況下,返回 code=401
if (StpUtil.isLogin() == false) {
return SaResult.code(401);
}
// 已登入情況下,構建 redirectUrl
if (SaSsoConsts.MODE_SIMPLE.equals(mode)) {
// 模式一
SaSsoUtil.checkRedirectUrl(SaFoxUtil.decoderUrl(redirect));
return SaResult.data(redirect);
} else {
// 模式二或模式三
String redirectUrl = SaSsoUtil.buildRedirectUrl(StpUtil.getLoginId(), redirect);
return SaResult.data(redirectUrl);
}
}
@RequestMapping("doLogin")
public SaResult doLogin(String name, String pwd) {
return authLoginUserService.queryUserNameAndPassword(name, pwd);
}
@RequestMapping(value = "isLogin", method = RequestMethod.GET)
public SaResult isLogin() {
return SaResult.ok("是否登入:" + StpUtil.isLogin());
}
@RequestMapping(value = "tokenInfo", method = RequestMethod.GET)
public SaResult tokenInfo() {
return SaResult.data(StpUtil.getTokenInfo());
}
@RequestMapping(value = "logout", method = RequestMethod.GET)
public SaResult logout() {
StpUtil.logout();
return SaResult.ok();
}
}
/**
* @description: 獲取當前賬號許可權碼集合
* @author: GuoTong
* @createTime: 2022-11-29 20:24
* @since JDK 1.8 OR 11
**/
@Component
@Slf4j
public class StpInterfaceImpl implements StpInterface {
@Autowired
private AuthLoginUserService authLoginUserService;
/**
* 返回一個賬號所擁有的許可權碼集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
AuthLoginUser user = null;
try {
user = authLoginUserService.getById(loginId.toString());
} catch (Exception e) {
log.info("Id無效--->{}", loginId);
}
assert user != null;
String rule = user.getRule();
return Collections.singletonList(rule);
}
/**
* 返回一個賬號所擁有的角色標識集合 (許可權與角色可分開校驗)
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
AuthLoginUser user = null;
try {
user = authLoginUserService.getById(loginId.toString());
} catch (Exception e) {
log.info("Id無效--->{}", loginId);
}
assert user != null;
String rule = user.getRule();
return Collections.singletonList(rule);
}
}
/**
* @description: 授權認證:HandlerInterceptor需要註冊攔截地址哦!!!
* @author: GuoTong
* @createTime: 2022-11-05 15:40
* @since JDK 1.8 OR 11
**/
@Component
public class AuthenticationInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
// 獲取當前對談是否已經登入,返回true=已登入,false=未登入
String requestURI = httpServletRequest.getRequestURI();
// 判斷是否是認證中心對外認證介面
if (requestURI.contains("/sso")) {
return true;
}
// 如果不是對映到方法直接通過
if (!(object instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) object;
Method method = handlerMethod.getMethod();
//檢查是否有SkipTokenByJWT註釋,有則跳過認證
if (method.isAnnotationPresent(SkipTokenByJWT.class)) {
SkipTokenByJWT SkipTokenByJWT = method.getAnnotation(SkipTokenByJWT.class);
if (SkipTokenByJWT.required()) {
return true;
}
}
// 否則需要登陸
if (!StpUtil.isLogin()) {
throw new NotLoginException(ContextCommonMsg.ERROR_MSG_4);
}
//檢查有沒有需要使用者許可權的註解
if (method.isAnnotationPresent(NeedTokenByJWT.class)) {
NeedTokenByJWT needTokenByJWT = method.getAnnotation(NeedTokenByJWT.class);
if (needTokenByJWT.required()) {
List<String> permissionList = StpUtil.getPermissionList();
// 檢視當前使用者是否包含當前介面的許可權
if (!permissionList.contains(needTokenByJWT.rule())) {
throw new NotLoginException(ContextCommonMsg.ERROR_MSG_3);
}
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o, Exception e) throws Exception {
}
}
/**
* @description: 全域性例外處理:
* @author: GuoTong
* @createTime: 2022-11-27 11:17
* @since JDK 1.8 OR 11
**/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
// 全域性異常攔截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
log.error("服務執行異常---->{}", e.getMessage());
return SaResult.error(e.getMessage());
}
@ExceptionHandler(value = NotLoginException.class)
public SaResult handlerException(NotLoginException e) {
log.error("沒有登陸---->{}", e.getMessage());
return SaResult.error(e.getMessage());
}
@ExceptionHandler(value = SQLException.class)
public SaResult msgMySQLExecuteError(Exception e) {
e.printStackTrace();
log.error("Mysql執行異常");
String message = e.getMessage();
return SaResult.error(message);
}
@ExceptionHandler(value = HttpMessageNotReadableException.class)
public SaResult msgNotFind(Exception e) {
e.printStackTrace();
log.error("請求錯誤");
String message = e.getMessage();
return SaResult.error("請求內容未傳遞" + message);
}
}
/**
* @description: SpringBoot-Web設定
* @author: GuoTong
* @createTime: 2023-06-05 15:37
* @since JDK 1.8 OR 11
**/
@Configuration
public class SpringBootConfig implements WebMvcConfigurer {
/**
* Description: 增加攔截器
*
* @param registry
* @author: GuoTong
* @date: 2022-11-30 13:56:44
* @return:void
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthenticationInterceptor()).addPathPatterns("/authLoginUser/**");
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
/**
* Description: 新增全域性跨域CORS處理
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
// 設定允許跨域的路徑
registry.addMapping("/**")
//設定允許跨域請求的域名
.allowedOriginPatterns("*")
// 是否允許證書
.allowCredentials(true)
// 設定允許的方法
.allowedMethods("GET", "POST", "DELETE", "PUT")
// 設定允許的header屬性
.allowedHeaders("*")
// 跨域允許時間
.maxAge(3600);
}
/**
* Description: 靜態資源過濾
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//ClassPath:/Static/** 靜態資源釋放
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
//釋放swagger
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
//釋放webjars
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
。。。。其餘的東西詳見專案。。。。。
https://gitee.com/gtnotgod/sa-token-sso-system.git
認證中心登入頁地址
介面管理認證中心使用者
<!--lombok-實體類簡化依賴-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- Sa-Token 許可權認證, 線上檔案:https://sa-token.cc -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${Sa-Token-version}</version>
</dependency>
<!-- Sa-Token 外掛:整合SSO -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-sso</artifactId>
<version>${Sa-Token-version}</version>
</dependency>
<!-- Sa-Token 整合redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${Sa-Token-version}</version>
</dependency>
<!-- Sa-Token外掛:許可權快取與業務快取分離 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-alone-redis</artifactId>
<version>${Sa-Token-version}</version>
</dependency>
<!--Open feign 服務間通訊HTTP-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>${spring-cloud-starter-version}</version>
</dependency>
<!--Http內建的JDKHttpURLConnection替換為OkHttp-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>${feign-okhttp-version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastJson-version}</version>
</dependency>
<!--web專案驅動-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot-start-version}</version>
</dependency>
<!--Knife4j(增強Swagger)-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<!-- bootstrap最高階啟動設定讀取 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
<version>${spring-cloud-starter-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
/**
* @description: 建立 SSO-Client 端認證介面
* @author: GuoTong
* @createTime: 2022-11-27 15:32
* @since JDK 1.8 OR 11
**/
@RestController
public class SSOClientController {
/*
* SSO-Client端:處理所有SSO相關請求
* http://{host}:{port}/sso/login -- Client端登入地址,接受引數:back=登入後的跳轉地址
* http://{host}:{port}/sso/logout -- Client端單點登出地址(isSlo=true時開啟),接受引數:back=登出後的跳轉地址
* http://{host}:{port}/sso/logoutCall -- Client端單點登出回撥地址(isSlo=true時開啟),此介面為框架回撥,開發者無需關心
*/
@RequestMapping("/sso/*")
public Object ssoRequest() {
return SaSsoProcessor.instance.clientDister();
}
// 首頁
@RequestMapping("/")
public String index() {
String str = "<h2>Sa-Token SSO-Client</h2>" +
"<p>當前對談是否登入:" + StpUtil.isLogin() + "</p>" +
"<p><a href=\"javascript:location.href='/sso/login?back=' + encodeURIComponent(location.href);\">Login</a> " +
"<a href='/sso/logout?back=self'>Logout</a></p>";
return str;
}
}
@ExceptionHandler(value = NotLoginException.class)
public Resp handlerException(NotLoginException e,
HttpServletRequest request,
HttpServletResponse response) {
log.error("沒有登陸---->{}", e.getMessage());
try {
response.sendRedirect("/sso/login?back=" + request.getRequestURL());
} catch (IOException ex) {
log.error("轉到認證中心失敗---->{}", ex.getMessage());
}
return Resp.error(e.getMessage());
}
spring:
redis:
# Redis資料庫索引(預設為0)
database: 1
# Redis伺服器地址
host: 127.0.0.1
# Redis伺服器連線埠
port: 6379
# Redis伺服器連線密碼(預設為空)
password: 123456
timeout: 10s
lettuce:
pool:
# 連線池最大連線數
max-active: 20
# 連線池最大阻塞等待時間(使用負值表示沒有限制)
max-wait: 10000
# 連線池中的最大空閒連線
max-idle: 3
# 連線池中的最小空閒連線
min-idle: 0
jackson:
date-format: yyyy-MM-dd HH:mm:ss
mvc:
pathmatch:
matching-strategy: ant_path_matcher #Springboot2.6以上需要手動設定
static-path-pattern: classpath:/static/**
main:
allow-bean-definition-overriding: true # 重複定義bean的問題
logging:
level:
root: info
org.springframework: info
# sa-token設定
sa-token:
# SSO-相關設定
sso:
# SSO-Server端 統一認證地址
auth-url: http://localhost:15001/sso/auth
# 是否開啟單點登出介面
is-slo: true
# 設定Sa-Token單獨使用的Redis連線 (此處需要和SSO-Server端連線同一個Redis)
alone-redis:
# Redis資料庫索引
database: 1
# Redis伺服器地址
host: 127.0.0.1
# Redis伺服器連線埠
port: 6379
# Redis伺服器連線密碼(預設為空)
password: 123456
# 連線超時時間
timeout: 10s
lettuce:
pool:
# 連線池最大連線數
max-active: 200
# 連線池最大阻塞等待時間(使用負值表示沒有限制)
max-wait: -1ms
# 連線池中的最大空閒連線
max-idle: 10
# 連線池中的最小空閒連線
min-idle: 0
forest:
log-enabled: false # 關閉 forest 請求紀錄檔列印
。。。。其餘的東西詳見專案。。。。。
https://gitee.com/gtnotgod/sa-token-sso-system.git
被重定向到認證中心去了
登入存取後
登出A服務 http://user.server.com:13601/sso/logout
登出B服務http://third.server.com:14302/sso/logout
具體介面程式碼:
/**
* 通過主鍵查詢單條資料
*
* @param id 主鍵
* @return 單條資料
*/
@GetMapping("/queryOne/{id}")
public Resp<Object> selectOne(@PathVariable("id") String id) {
return Resp.Ok(this.openFeignRPCMySQlService.selectOne(id));
}
fegin介面
/**
* @description: OpenFegin呼叫third-party-service服務
* @FeignClient(name = "third-party-service") name指定呼叫的服務名
* @author: GuoTong
* @createTime: 2022-12-02 19:39
* @since JDK 1.8 OR 11
**/
@FeignClient(name = "third-party-service")
public interface OpenFeignRPCMySQlService {
/**
* Description: feign呼叫third-party-service服務的介面
*
* @author: GuoTong
* @date: 2022-12-02 20:56:28
* @return:
*/
@GetMapping(value = "/hello/getOne2/{id}", produces = "application/json;charset=utf-8")
Resp<Object> selectOne(@PathVariable("id") String id);
}
重定向認證中心
登入A系統
fegin呼叫成功B系統
不再重定向到認證中心,A已經登入,B已完成單點,校驗已持有登入狀態
已經重定向到認證中心
fegin呼叫A系統 :http://third.server.com:14302/hello/queryOne/100011
依舊重定向到認證中心
個人專案地址,在gitee:https://gitee.com/gtnotgod/sa-token-sso-system.git
tip: 更多Sa-Token使用教學參考官方地址:https://sa-token.cc/doc.html
----------隔壁老郭還有大號:隔壁老郭---------------------------------
個性簽名:獨學而無友,則孤陋而寡聞。做一個靈魂有趣的人!
如果覺得這篇文章對你有小小的幫助的話,記得在右下角點個「推薦」哦,博主在此感謝!
萬水千山總是情,打賞一分行不行,所以如果你心情還比較高興,也是可以掃碼打賞博主,哈哈哈(っ•̀ω•́)っ✎⁾⁾!