Java spring單點登錄系統(tǒng)
1.單點登錄系統(tǒng)介紹
多點登陸系統(tǒng)。應用起來相對繁瑣(每次訪問資源服務都需要重新登陸認證和授權(quán))。與此同時,系統(tǒng)代碼的重復也比較高。所以單點登錄系統(tǒng),倍受歡迎!
單點登錄系統(tǒng),即多個站點共用一臺認證授權(quán)服務器,用戶在其中任何一個站點登錄后,可以免登錄訪問其他所有站點。而且,各站點間可以通過該登錄狀態(tài)直接交互。

2.簡單業(yè)務實現(xiàn)
在文件上傳的項目添加認證授權(quán)服務,義登錄頁面(login.html),然后在頁面中輸入自己的登陸賬號,登陸密碼,將請求提交給網(wǎng)關(guān),然后網(wǎng)關(guān)將請求轉(zhuǎn)發(fā)到auth工程,登陸成功和失敗要返回json數(shù)據(jù),按照如下結(jié)構(gòu)實現(xiàn)

在02-sca工程創(chuàng)建 sca-auth子module,作為認證授權(quán)服務

2.1添加依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
2.2 項目配置文件
在sca-auth工程中創(chuàng)建bootstrap.yml文件
server:
port: 8071
spring:
application:
name: sca-auth
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848
2.3添加項目啟動類
package com.jt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ResourceAuthApplication {
public static void main(String[] args) {
SpringApplication.run(ResourceAuthApplication.class, args);
}
}
2.4 啟動并訪問項目
項目啟動時,系統(tǒng)會默認生成一個登陸密碼

打開瀏覽器輸入http://localhost:8071呈現(xiàn)登陸頁面

默認用戶名為user,密碼為系統(tǒng)啟動時,在控制臺呈現(xiàn)的密碼。執(zhí)行登陸測試,登陸成功進入如下界面(因為沒有定義登陸頁面,所以會出現(xiàn)404)
3. 優(yōu)化進一步設計
3.1 定義安全配置類 SecurityConfig
修改SecurityConfig配置類,添加登錄成功或失敗的處理邏輯
package com.jt.auth.config;
import com.fasterxml.jackson.databind.ObjectMapper;
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.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
@Configuration//配置對象--系統(tǒng)啟動時底層會產(chǎn)生代理對象,來初始化一些對象
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//WebSecurityConfigurerAdapter 類是個適配器, 在配置的時候,需要我們自己寫個配置類去繼承他,然后編寫自己所特殊需要的配置
//BCryptPasswordEncoder密碼加密對象比MD5安全性更高,MD5暴力反射可以破解過
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 配置認證管理器(負責對客戶輸入的用戶信息進行認證),在其他配置類中會用到這個對象
* @return
* @throws Exception
*/
@Bean
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
/**在這個方法中定義登錄規(guī)則
* 1)對所有請求放行(當前工程只做認證)
* 2)登錄成功信息的返回
* 3)登錄失敗信息的返回
* */
@Override
protected void configure(HttpSecurity http) throws Exception {
//禁用跨域
http.csrf().disable();
//放行所有請求
http.authorizeRequests().anyRequest().permitAll();
//登錄成功與失敗的處理
http.formLogin()
.successHandler(successHandler()) // .successHandler(AuthenticationSuccessHandler對象)
.failureHandler(failureHandler());
}
@Bean
//構(gòu)建successHandler()方法來創(chuàng)建AuthenticationSuccessHandler對象
public AuthenticationSuccessHandler successHandler(){
// return new AuthenticationSuccessHandler() {
// @Override
// public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
//
// }
// }
return (request,response,authentication) ->{
//1.構(gòu)建map對象,封裝響應數(shù)據(jù)
Map<String,Object> map=new HashMap<>();
map.put("state",200);
map.put("message","login ok"); //登錄成功返回的響應信息
//2.將map對象寫到客戶端
writeJsonToClient(response,map);
};
}
@Bean
//failureHandler();方法來創(chuàng)建AuthenticationSuccessHandler對象
public AuthenticationFailureHandler failureHandler(){
return (request,response, e)-> {
//1.構(gòu)建map對象,封裝響應數(shù)據(jù)
Map<String,Object> map=new HashMap<>();
map.put("state",500);
map.put("message","login failure");//登錄失敗返回的響應信息
//2.將map對象寫到客戶端
writeJsonToClient(response,map);
};
}
//提取公共代碼,將對象轉(zhuǎn)為Json傳給客戶端, 構(gòu)建writeJsonToClient();
private void writeJsonToClient(HttpServletResponse response,
Object object) throws IOException { // Object 類型,不只是Map類型,說不準
//2.將對象轉(zhuǎn)換為json
//Gson-->toJson (需要自己找依賴)
//fastjson-->JSON (spring-cloud-starter-alibaba-sentinel)
//jackson-->writeValueAsString (spring-boot-starter-web)
String jsonStr=new ObjectMapper().writeValueAsString(object);
//3.將json字符串寫到客戶端
PrintWriter writer = response.getWriter();
writer.println(jsonStr);
writer.flush();
}
}
3.2定義用戶信息處理對象
正常來說,用來與數(shù)據(jù)庫中的用戶信息作對比,認證是否正確,可否授權(quán)

package com.jt.auth.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 登錄時用戶信息的獲取和封裝會在此對象進行實現(xiàn),
* 在頁面上點擊登錄按鈕時,會調(diào)用這個對象的loadUserByUsername方法,
* 頁面上輸入的用戶名會傳給這個方法的參數(shù)
*
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {//獲取用戶詳細信息的接口
@Autowired
//BCryptPasswordEncoder密碼加密對象
private BCryptPasswordEncoder passwordEncoder;
//UserDetails用戶封裝用戶信息(認證和權(quán)限信息)
@Override
//重寫UserDetailsService 接口中的 loadUserByUsername();方法,定義用來核對數(shù)據(jù)庫數(shù)據(jù)和授于相應的權(quán)限
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
//1.基于用戶名查詢用戶信息(用戶名,用戶狀態(tài),密碼,....)
//Userinfo userinfo=userMapper.selectUserByUsername(username);數(shù)據(jù)庫用戶信息查詢操作簡寫了
String encodedPassword=passwordEncoder.encode("123456");
//2.查詢用戶權(quán)限信息(后面訪問數(shù)據(jù)庫)
//這里先給幾個假數(shù)據(jù)
List<GrantedAuthority> authorities =
AuthorityUtils.createAuthorityList(//這里的權(quán)限信息先這么寫,后面講
"sys:res:create", "sys:res:retrieve");
//3.對用戶信息進行封裝
return new User(username,encodedPassword,authorities);
}
}
3.3 網(wǎng)關(guān)中登陸路由配置
在網(wǎng)關(guān)配置文件中添加如下配置
server:
port: 9001
spring:
application:
name: sca-resource-gateway
cloud:
sentinel: #限流設計
transport:
dashboard: localhost:8180
eager: true
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848
file-extension: yml
gateway:
discovery:
locator:
enabled: true
routes:
- id: router02
uri: lb://sca-auth #lb表示負載均衡,底層默認使用ribbon實現(xiàn)
predicates: #定義請求規(guī)則(請求需要按照此規(guī)則設計)
- Path=/auth/login/** #請求路徑設計,單體架構(gòu)
filters:
- StripPrefix=1 #轉(zhuǎn)發(fā)之前去掉path中第一層路徑
3.4基于Postman進行訪問測試
啟動sca-gateway,sca-auth服務,然后基于postman進行訪問測試

3.5 定義登陸頁面
在sca-resource-ui工程的static目錄中定義login-sso.html 登陸頁面
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<link rel="external nofollow" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>login</title>
</head>
<body>
<div class="container"id="app">
<h3>Please Login</h3>
<form>
<div class="mb-3">
<label for="usernameId" class="form-label">Username</label>
<input type="text" v-model="username" class="form-control" id="usernameId" aria-describedby="emailHelp">
</div>
<div class="mb-3">
<label for="passwordId" class="form-label">Password</label>
<input type="password" v-model="password" class="form-control" id="passwordId">
</div>
<button type="button" @click="doLogin()" class="btn btn-primary">Submit</button>
</form>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
var vm=new Vue({
el:"#app",//定義監(jiān)控點,vue底層會基于此監(jiān)控點在內(nèi)存中構(gòu)建dom樹
data:{ //此對象中定義頁面上要操作的數(shù)據(jù)
username:"",
password:""
},
methods: {//此位置定義所有業(yè)務事件處理函數(shù)
doLogin() {
//1.定義url
let url = "http://localhost:9001/auth/oauth/token"
//2.定義參數(shù)
let params = new URLSearchParams()
params.append('username',this.username);
params.append('password',this.password);
params.append("client_id","gateway-client");
params.append("grant_type","password");
params.append("client_secret","123456");
//3.發(fā)送異步請求
axios.post(url, params).then((response) => {
debugger
console.log(response.data);
let result=response.data;
//
localStorage.setItem("accessToken",result.access_token);
location.href="/fileupload.html" rel="external nofollow"
})
}
}
});
</script>
</body>
</html>
3.6 構(gòu)建令牌配置對象
借助JWT(Json Web Token-是一種json格式)方式將用戶信息轉(zhuǎn)換為json格式,然后進行加密,保存用戶信息到客戶端,然后發(fā)送在客戶端客戶端接收到這個JWT之后,保存在客戶端,之后帶著JWT訪問其它模塊時,資源服務器解析獲得用戶信息,進行訪問,達到解放內(nèi)存的目的
config 目錄下 TokenConfig類
package com.jt.auth.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;
/*
*創(chuàng)建jwt類型令牌
*構(gòu)建令牌的構(gòu)成有三部分:
* header(頭部信息:令牌類型)/
* payload(數(shù)據(jù)信息-用戶信息,權(quán)限信息)/
* SIGNATURE(簽名信息-對header和payload部分加密)
* */
@Configuration //配置對象--系統(tǒng)啟動時底層會產(chǎn)生代理對象,來初始化一些對象
public class TokenConfig {
//定義令牌簽發(fā)口令(暗號,規(guī)則),解密口令
//當客戶端在執(zhí)行登錄時,加入有攜帶這個信息,認證服務器可以簽發(fā)令牌
private String SIGNING_KEY = "auth"; 在對header和payload簽名時,需要的一個口令
//構(gòu)建令牌生成器對象()
@Bean
public TokenStore tokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());//令牌生成器(jwt轉(zhuǎn)換器)
}
@Bean
//Jwt轉(zhuǎn)換器,將任何數(shù)據(jù)轉(zhuǎn)換為jwt字符串
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter converter=new JwtAccessTokenConverter();
//設置加密/解密口令
converter.setSigningKey(SIGNING_KEY);
return converter;
}
}

創(chuàng)建認證管理器對象
在SecurityConfig中添加如下方法(后面授權(quán)服務器會用到):
/**
* 配置認證管理器(負責對客戶輸入的用戶信息進行認證),在其他配置類中會用到這個對象
* @return
* @throws Exception
*/
@Bean
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
3.7 定義認證授權(quán)核心配置
授權(quán)服務器的核心配置
package com.jt.auth.config;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.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.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import java.util.Arrays;
/**
* 完成所有配置的組裝,在這個配置類中完成認證授權(quán),JWT令牌簽發(fā)等配置操作
* 1)SpringSecurity (安全認證和授權(quán))
* 2)TokenConfig
* 3)Oauth2(暫時不說)
*/
@AllArgsConstructor
@Configuration
@EnableAuthorizationServer //開啟認證和授權(quán)服務
public class Oauth2Config extends AuthorizationServerConfigurerAdapter {
//此對象負責完成認證管理
private AuthenticationManager authenticationManager;
//TokenStore負責完成令牌創(chuàng)建,信息讀取
private TokenStore tokenStore;
//負責獲取用戶詳情信息(username,password,client_id,grant_type,client_secret)
//private ClientDetailsService clientDetailsService;
//JWT令牌轉(zhuǎn)換器(基于用戶信息構(gòu)建令牌,解析令牌)
private JwtAccessTokenConverter jwtAccessTokenConverter;
//密碼加密匹配器對象
private PasswordEncoder passwordEncoder;
//負責獲取用戶信息信息
private UserDetailsService userDetailsService;
//設置認證端點的配置(/oauth/token),客戶端通過這個路徑獲取JWT令牌
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
//配置認證管理器
.authenticationManager(authenticationManager)
//驗證用戶的方法獲得用戶詳情
.userDetailsService(userDetailsService)
//要求提交認證使用post請求方式,提高安全性
.allowedTokenEndpointRequestMethods(HttpMethod.POST,HttpMethod.GET)
//要配置令牌的生成,由于令牌生成比較復雜,下面有方法實現(xiàn)
.tokenServices(tokenService());//這個不配置,默認令牌為UUID.randomUUID().toString()
}
//定義令牌生成策略
@Bean
public AuthorizationServerTokenServices tokenService(){
//這個方法的目標就是獲得一個令牌生成器
DefaultTokenServices services=new DefaultTokenServices();
//支持令牌刷新策略(令牌有過期時間)
services.setSupportRefreshToken(true);
//設置令牌生成策略(tokenStore在TokenConfig配置了,本次我們應用JWT-定義了一種令牌格式)
services.setTokenStore(tokenStore);
//設置令牌增強(固定用法-令牌Payload部分允許添加擴展數(shù)據(jù),例如用戶權(quán)限信息)
TokenEnhancerChain chain=new TokenEnhancerChain();
chain.setTokenEnhancers(
Arrays.asList(jwtAccessTokenConverter));
//令牌增強對象設置到令牌生成
services.setTokenEnhancer(chain);
//設置令牌有效期
services.setAccessTokenValiditySeconds(3600);//1小時
//刷新令牌應用場景:一般在用戶登錄系統(tǒng)后,令牌快過期時,系統(tǒng)自動幫助用戶刷新令牌,提高用戶的體驗感
services.setRefreshTokenValiditySeconds(3600*72);//3天
//配置客戶端詳情
//services.setClientDetailsService(clientDetailsService);
return services;
}
//設置客戶端詳情類似于用戶詳情
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
//客戶端id
.withClient("gateway-client")
//客戶端秘鑰
.secret(passwordEncoder.encode("123456"))
//設置權(quán)限
.scopes("all")//all只是個名字而已和寫abc效果相同
//允許客戶端進行的操作 里面的字符串千萬不能寫錯
.authorizedGrantTypes("password","refresh_token");
}
// 認證成功后的安全約束配置
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//認證通過后,允許客戶端進行哪些操作
security
//公開oauth/token_key端點
.tokenKeyAccess("permitAll()")
//公開oauth/check_token端點
.checkTokenAccess("permitAll()")
//允許提交請求進行認證(申請令牌)
.allowFormAuthenticationForClients();
}
}
Postman訪問測試
第一步:啟動服務
依次啟動sca-auth服務,sca-resource-gateway,sca-resource-ui服務。
第二步:檢測sca-auth服務控制臺的Endpoints信息,例如:

打開postman進行登陸訪問測試

{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MzAwNzYxMTAsInVzZXJfbmFtZSI6ImphY2siLCJhdXRob3JpdGllcyI6WyJzeXM6cmVzOmNyZWF0ZSIsInN5czpyZXM6cmV0cmlldmUiXSwianRpIjoiM2Q0MzExOTYtYmRkZi00Y2NhLWFmMDMtNWMzNGM4ZmJkNzQ3IiwiY2xpZW50X2lkIjoiZ2F0ZXdheS1jbGllbnQiLCJzY29wZSI6WyJhbGwiXX0.GnrlqsZMSdagDaRQDZWDLbY7I7KUlXQgyXATcXXS6FI",
"token_type": "bearer",
4 資源服務器配置–sca-resource

4.1 構(gòu)建令牌配置對象
package com.jt.resource.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
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;
/*
*創(chuàng)建jwt類型令牌
*構(gòu)建令牌的構(gòu)成有三部分:
* header(頭部信息:令牌類型)/
* payload(數(shù)據(jù)信息-用戶信息,權(quán)限信息)/
* SIGNATURE(簽名信息-對header和payload部分加密)
* */
@Configuration //配置對象--系統(tǒng)啟動時底層會產(chǎn)生代理對象,來初始化一些對象
public class TokenConfig {
//定義令牌簽發(fā)口令(暗號,規(guī)則),解密口令
//當客戶端在執(zhí)行登錄時,加入有攜帶這個信息,認證服務器可以簽發(fā)令牌
//在對header和payload簽名時
private String SIGNING_KEY = "auth";
//構(gòu)建令牌生成器對象()
@Bean
public TokenStore tokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());//令牌生成器(轉(zhuǎn)換器)
}
//Jwt轉(zhuǎn)換器,將任何數(shù)據(jù)轉(zhuǎn)換為jwt字符串
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter converter=new JwtAccessTokenConverter();
//設置加密/解密口令
converter.setSigningKey(SIGNING_KEY);
return converter;
}
}
4.2 資源服務令牌解析配置
2.將對象轉(zhuǎn)換為json
//Gson-->toJson (需要自己找依賴)
//fastjson-->JSON (spring-cloud-starter-alibaba-sentinel)
//jackson-->writeValueAsString (spring-boot-starter-web)
package com.jt.resource.config;
import com.alibaba.fastjson.JSON;
import com.google.gson.Gson;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
/**
* 資源服務器的配置,在這個對象
* 1)JWT 令牌解析配置(客戶端帶著令牌訪問資源時,要對令牌進行解析)
* 2) 啟動資源訪問的授權(quán)配置(不是所有登陸用戶可以訪問所有資源)
*/
@Configuration
@EnableResourceServer 此注解會啟動資源服務器的默認配置
@EnableGlobalMethodSecurity(prePostEnabled = true) //執(zhí)行方法之前啟動權(quán)限檢查
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
/**
* token服務配置
*/
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
//super.configure(resources);
//定義令牌生成策略,這里不是要創(chuàng)建令牌,是要解析令牌
resources.tokenStore(tokenStore);
}
/**
* 路由安全認證配置
*/
@Override
public void configure(HttpSecurity http) throws Exception {
//super.configure(http);
http.csrf().disable();//關(guān)閉跨域攻擊
//放行所有的資源訪問(對資源的方問不做認證)
http.authorizeRequests().anyRequest().permitAll();
//http.authorizeRequests().mvcMatchers("/resource/**")
// .authenticated(); //假如沒有認證就訪問會報401異常
//處理異常
http.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler()); //403的異常處理 .拒絕訪問的處理(AccessDeniedHandler類型對象)
}
@Bean
public AccessDeniedHandler accessDeniedHandler() { //返回 AccessDeniedHandler對象
return (request,response, exception)->{
Map<String,Object> map = new HashMap<>();
map.put("state", HttpServletResponse.SC_FORBIDDEN);//403
map.put("message", "抱歉,沒有這個資源");
//1設置響應數(shù)據(jù)的編碼
response.setCharacterEncoding("utf-8");
//2告訴瀏覽器響應數(shù)據(jù)的內(nèi)容類型以及編碼
response.setContentType("application/json;charset=utf-8");
//2.將對象轉(zhuǎn)換為json
//1.fastjson-->JSON (spring-cloud-starter-alibaba-sentinel)
//String jsonStr= JSON.toJSONString(map);//fastjson
//2.Gson-->toJson (需要自己找依賴)
Gson gson = new Gson();
String jsonStr = gson.toJson(map);
//jackson-->writeValueAsString (spring-boot-starter-web)
//String jsonStr = new ObjectMapper().writeValueAsString(map);
PrintWriter writer = response.getWriter();
writer.println(jsonStr);
writer.flush();
};
}
}
4.3 設置資源訪問的權(quán)限
在ResourceController的上傳方法上添加 @PreAuthorize(“hasAuthority(‘sys:res:create')”)注解,用于告訴底層框架方法此方法需要具備的權(quán)限,
@PreAuthorize("hasAuthority('sys:res:create')")
@PostMapping("/upload/")
public String uploadFile(MultipartFile uploadFile) throws IOException {
...
}
不加權(quán)限,會報403異常,并且展示我們修改403異常的信息返回在控制臺上
4.4 啟動服務訪問測試
4.4.1 訪問auth認證授權(quán)服務獲取token
啟動服務(sca-auth,sca-resource-gateway,sca-resource)
執(zhí)行POSTMAN ,訪問 auth認證授權(quán)服務 http://localhost:9001/auth/oauth/token, 獲取token

4.4.2 攜帶TOKEN訪問資源服務器
復制access_token ,請求地址: http://localhost:9001/sca/resource/upload/
1.設置請求頭(header),要攜帶令牌并指定請求的內(nèi)容類型

2. 設置請求體(body),設置form-data,key要求為file類型,參數(shù)名與你服務端controller文件上傳方法的參數(shù)名相同,值為你選擇的文件

4.4.3 對403異常前端頁面顯示
function upload(file){
//定義一個表單
let form=new FormData();
//將圖片添加到表單中
form.append("uploadFile",file);
let url="http://localhost:9000/sca/resource/upload/";
//異步提交方式1
axios.post(url,form,{headers:{"Authorization":"Bearer "+localStorage.getItem("accessToken")}})
.then(function (response){
let result=response.data;
if(result.state==403){
alert(result.message);
return;
}
alert("upload ok");
})
}
1.啟動服務(sca-auth,sca-resource-gateway,sca-resource)
2.執(zhí)行登陸 localhost:8080/login-sso.html 獲取access_token令牌
3.攜帶令牌訪問資源(url中的前綴"sca"是在資源服務器中自己指定的,你的網(wǎng)關(guān)怎么配置的,你就怎么寫)

成功:

403異常,沒有訪問權(quán)限

4.5 Oauth2規(guī)范
oauth2定義了一種認證授權(quán)協(xié)議,一種規(guī)范,此規(guī)范中定義了四種類型的角色:
1)資源有者(User)
2)認證授權(quán)服務器(jt-auth)
3)資源服務器(jt-resource)
4)客戶端應用(jt-ui)
同時,在這種協(xié)議中規(guī)定了認證授權(quán)時的幾種模式:
1)密碼模式 (基于用戶名和密碼進行認證)
2)授權(quán)碼模式(就是我們說的三方認證:QQ,微信,微博,。。。。)
3)…
4.6 面試問題點
單點登陸系統(tǒng)的設計架構(gòu)(微服務架構(gòu))
服務的設計及劃分(資源服務器,認證服務器,網(wǎng)關(guān)服務器,客戶端服務)
認證及資源訪問的流程(資源訪問時要先認證再訪問)
認證和授權(quán)時的一些關(guān)鍵技術(shù)(Spring Security,Jwt,Oauth2)
FAQ 分析
為什么要單點登陸(分布式系統(tǒng),再訪問不同服務資源時,不要總是要登陸,進而改善用戶體驗)
單點登陸解決方案?(市場常用兩種: spring security+jwt+oauth2,spring securit+redis+oauth2)
Spring Security 是什么?(spring框架中的一個安全默認,實現(xiàn)了認證和授權(quán)操作)
JWT是什么?(一種令牌格式,一種令牌規(guī)范,通過對JSON數(shù)據(jù)采用一定的編碼,加密進行令牌設計)
OAuth2是什么?(一種認證和授權(quán)規(guī)范,定義了單點登陸中服務的劃分方式,認證的相關(guān)類型)
…
5 Bug 分析
401 : 訪問資源時沒有認證。
403 : 訪問資源時沒有權(quán)限。
404:訪問的資源找不到(一定要檢查你訪問資源的url)
405: 請求方式不匹配(客戶端請求方式是GET,服務端處理請求是Post就是這個問題)
500: 不看后臺無法解決?(error,warn)
…
到此這篇關(guān)于Java spring單點登錄系統(tǒng)的文章就介紹到這了,更多相關(guān)Java單點登錄內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis實現(xiàn)動態(tài)增刪改查功能的示例代碼
這篇文章主要介紹了Mybatis實現(xiàn)動態(tài)增刪改查功能的示例代碼,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04
Java并發(fā)編程之synchronized底層實現(xiàn)原理分析
這篇文章主要介紹了Java并發(fā)編程之synchronized底層實現(xiàn)原理,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-02-02
SpringBoot條件注解@Conditional詳細解析
這篇文章主要介紹了SpringBoot條件注解@Conditional詳細解析,@Conditional是Spring4.0提供的一個用于條件裝配的注解,其定義了一個Condition的數(shù)組,只有當數(shù)組所有的條件都滿足的時候,組件才會被導入容器,需要的朋友可以參考下2023-11-11
springboot3生成本地文件url的實現(xiàn)示例
本文主要介紹了springboot3生成本地文件url的實現(xiàn)示例,從而提供一種高效的文件管理方式,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2024-01-01

