Spring Security基于JWT實現SSO單點登錄詳解
SSO :同一個帳號在同一個公司不同系統(tǒng)上登陸
使用SpringSecurity實現類似于SSO登陸系統(tǒng)是十分簡單的 下面我就搭建一個DEMO
首先來看看目錄的結構
其中sso-demo是父工程項目 sso-client 、sso-client2分別對應2個資源服務器,sso-server是認證服務器
引入的pom文件
sso-demo
<?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>study.security.sso</groupId>
<artifactId>sso-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<modules>
<module>sso-server</module>
<module>sso-client</module>
<module>sso-client2</module>
</modules>
<packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>Brussels-SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
sso-server
<?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">
<parent>
<artifactId>sso-demo</artifactId>
<groupId>study.security.sso</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sso-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
</dependencies>
</project>
sso-client與sso-client2 pom 中的 是一樣的
1.sso-server
現在開始搭建認證服務器
認證服務器的目錄結構如下

/**
* 認證服務器配置
* Created by ZhuPengWei on 2018/1/11.
*/
@Configuration
@EnableAuthorizationServer
public class SsoAuthenticationServerConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client1")
.secret("client1")
.authorizedGrantTypes("authorization_code", "refresh_token")
.scopes("all")
.and()
.withClient("client2")
.secret("client2")
.authorizedGrantTypes("authorization_code", "refresh_token")
.scopes("all");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(jwtTokenStore()).accessTokenConverter(jwtAccessTokenConverter());
}
/**
* 認證服務器的安全配置
*
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// 要訪問認證服務器tokenKey的時候需要經過身份認證
security.tokenKeyAccess("isAuthenticated()");
}
@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
// 保證JWT安全的唯一方式
jwtAccessTokenConverter.setSigningKey("ZPW");
return jwtAccessTokenConverter;
}
}
/**
* 自定義用戶登陸邏輯配置
* Created by ZhuPengWei on 2018/1/13.
*/
@Configuration
public class SsoSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
/**
* 加密解密邏輯
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 改成表單登陸的方式 所有請求都需要認證
http.formLogin().and().authorizeRequests().anyRequest().authenticated();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 用自己的登陸邏輯以及加密器
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
}
/**
* 自定義用戶登陸
* Created by ZhuPengWei on 2018/1/13.
*/
@Component
public class SsoUserDetailsService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User(username,
passwordEncoder.encode("123456"),
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
}
}
其中SsoApprovalEndPoint與SsoSpelView目的是去掉登陸之后授權的效果
注解 @FrameworkEndpoint
與@RestController注解相類似
如果聲明和@FrameworkEndpoint一模一樣的@RequestMapping
Spring框架處理的時候會優(yōu)先處理@RestController里面的

/**
* 自定義認證邏輯
* Created by ZhuPengWei on 2018/1/13.
*/
@RestController
@SessionAttributes("authorizationRequest")
public class SsoApprovalEndpoint {
@RequestMapping("/oauth/confirm_access")
public ModelAndView getAccessConfirmation(Map<String, Object> model, HttpServletRequest request) throws Exception {
String template = createTemplate(model, request);
if (request.getAttribute("_csrf") != null) {
model.put("_csrf", request.getAttribute("_csrf"));
}
return new ModelAndView(new SsoSpelView(template), model);
}
protected String createTemplate(Map<String, Object> model, HttpServletRequest request) {
String template = TEMPLATE;
if (model.containsKey("scopes") || request.getAttribute("scopes") != null) {
template = template.replace("%scopes%", createScopes(model, request)).replace("%denial%", "");
} else {
template = template.replace("%scopes%", "").replace("%denial%", DENIAL);
}
if (model.containsKey("_csrf") || request.getAttribute("_csrf") != null) {
template = template.replace("%csrf%", CSRF);
} else {
template = template.replace("%csrf%", "");
}
return template;
}
private CharSequence createScopes(Map<String, Object> model, HttpServletRequest request) {
StringBuilder builder = new StringBuilder("<ul>");
@SuppressWarnings("unchecked")
Map<String, String> scopes = (Map<String, String>) (model.containsKey("scopes") ? model.get("scopes") : request
.getAttribute("scopes"));
for (String scope : scopes.keySet()) {
String approved = "true".equals(scopes.get(scope)) ? " checked" : "";
String denied = !"true".equals(scopes.get(scope)) ? " checked" : "";
String value = SCOPE.replace("%scope%", scope).replace("%key%", scope).replace("%approved%", approved)
.replace("%denied%", denied);
builder.append(value);
}
builder.append("</ul>");
return builder.toString();
}
private static String CSRF = "<input type='hidden' name='${_csrf.parameterName}' value='${_csrf.token}' />";
private static String DENIAL = "<form id='denialForm' name='denialForm' action='${path}/oauth/authorize' method='post'><input name='user_oauth_approval' value='false' type='hidden'/>%csrf%<label><input name='deny' value='Deny' type='submit'/></label></form>";
// 對源代碼進行處理 隱藏授權頁面,并且使他自動提交
private static String TEMPLATE = "<html><body><div style='display:none;'> <h1>OAuth Approval</h1>"
+ "<p>Do you authorize '${authorizationRequest.clientId}' to access your protected resources?</p>"
+ "<form id='confirmationForm' name='confirmationForm' action='${path}/oauth/authorize' method='post'><input name='user_oauth_approval' value='true' type='hidden'/>%csrf%%scopes%<label><input name='authorize' value='Authorize' type='submit'/></label></form>"
+ "%denial%</div><script>document.getElementById('confirmationForm').submit();</script></body></html>";
private static String SCOPE = "<li><div class='form-group'>%scope%: <input type='radio' name='%key%'"
+ " value='true'%approved%>Approve</input> <input type='radio' name='%key%' value='false'%denied%>Deny</input></div></li>";
}
SsoSpelView 與 原來SpelView 是一樣的 只不過原來SpelView 不是public的類
application.properties
server.port=9999 server.context-path=/server
2.sso-client
相對于認證服務器 資源服務器demo的配置就十分簡單了

/**
* Created by ZhuPengWei on 2018/1/11.
*/
@SpringBootApplication
@RestController
@EnableOAuth2Sso
public class SsoClient1Application {
@GetMapping("/user")
public Authentication user(Authentication user) {
return user;
}
public static void main(String[] args) {
SpringApplication.run(SsoClient1Application.class, args);
}
}
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>SSO Client1</title> </head> <body> <h1>SSO Demo Client1</h1> <a rel="external nofollow" >訪問client2</a> </body> </html>
application.properties
security.oauth2.client.client-id=client1 security.oauth2.client.client-secret=client1 #需要認證時候跳轉的地址 security.oauth2.client.user-authorization-uri=http://127.0.0.1:9999/server/oauth/authorize #請求令牌地址 security.oauth2.client.access-token-uri=http://127.0.0.1:9999/server/oauth/token #解析 security.oauth2.resource.jwt.key-uri=http://127.0.0.1:9999/server/oauth/token_key #sso server.port=8080 server.context-path=/client1
3.sso-client2
資源服務器1和資源服務器2的目錄結構是一樣的,改了相關的參數
/**
* Created by ZhuPengWei on 2018/1/11.
*/
@SpringBootApplication
@RestController
@EnableOAuth2Sso
public class SsoClient2Application {
@GetMapping("/user")
public Authentication user(Authentication user) {
return user;
}
public static void main(String[] args) {
SpringApplication.run(SsoClient2Application.class, args);
}
}
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>SSO Client2</title> </head> <body> <h1>SSO Demo Client2</h1> <a rel="external nofollow" >訪問client1</a> </body> </html>
security.oauth2.client.client-id=client2 security.oauth2.client.client-secret=client2 #需要認證時候跳轉的地址 security.oauth2.client.user-authorization-uri=http://127.0.0.1:9999/server/oauth/authorize #請求令牌地址 security.oauth2.client.access-token-uri=http://127.0.0.1:9999/server/oauth/token #解析 security.oauth2.resource.jwt.key-uri=http://127.0.0.1:9999/server/oauth/token_key #sso server.port=8060 server.context-path=/client2
好了 基于JWT實現SSO單點登錄的DEMO以及搭建完成了 下面來看看頁面的效果
在初次訪問的時候
圖1

登陸成功之后
圖2

圖3

注意
寫SsoApprovalEndPoint與SsoSpelView目的是去掉登陸之后授權的效果如果不寫這2個類
在初次訪問的登陸成功之后是有一步授權的操作的
比如說圖1操作成功之后
點擊Authorize才會跳轉到圖2
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
基于Spring的Maven項目實現發(fā)送郵件功能的示例
這篇文章主要介紹了基于Spring的Maven項目實現發(fā)送郵件功能,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-03-03
使用spring+maven不同環(huán)境讀取配置方式
這篇文章主要介紹了使用spring+maven不同環(huán)境讀取配置方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-08-08
關于SpringBoot大文件RestTemplate下載解決方案
這篇文章主要介紹了SpringBoot大文件RestTemplate下載解決方案,最近結合網上案例及自己總結,寫了一個分片下載tuling/fileServer項目,需要的朋友可以參考下2021-10-10

