Spring Security基于JWT實(shí)現(xiàn)SSO單點(diǎn)登錄詳解
SSO :同一個帳號在同一個公司不同系統(tǒng)上登陸
使用SpringSecurity實(shí)現(xiàn)類似于SSO登陸系統(tǒng)是十分簡單的 下面我就搭建一個DEMO
首先來看看目錄的結(jié)構(gòu)
其中sso-demo是父工程項目 sso-client 、sso-client2分別對應(yīng)2個資源服務(wù)器,sso-server是認(rèn)證服務(wù)器
引入的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
現(xiàn)在開始搭建認(rèn)證服務(wù)器
認(rèn)證服務(wù)器的目錄結(jié)構(gòu)如下
/** * 認(rèn)證服務(wù)器配置 * 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()); } /** * 認(rèn)證服務(wù)器的安全配置 * * @param security * @throws Exception */ @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { // 要訪問認(rèn)證服務(wù)器tokenKey的時候需要經(jīng)過身份認(rèn)證 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 { // 改成表單登陸的方式 所有請求都需要認(rèn)證 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目的是去掉登陸之后授權(quán)的效果
注解 @FrameworkEndpoint
與@RestController注解相類似
如果聲明和@FrameworkEndpoint一模一樣的@RequestMapping
Spring框架處理的時候會優(yōu)先處理@RestController里面的
/** * 自定義認(rèn)證邏輯 * 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>"; // 對源代碼進(jìn)行處理 隱藏授權(quán)頁面,并且使他自動提交 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
相對于認(rèn)證服務(wù)器 資源服務(wù)器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 #需要認(rèn)證時候跳轉(zhuǎn)的地址 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
資源服務(wù)器1和資源服務(wù)器2的目錄結(jié)構(gòu)是一樣的,改了相關(guān)的參數(shù)
/** * 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 #需要認(rèn)證時候跳轉(zhuǎn)的地址 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實(shí)現(xiàn)SSO單點(diǎn)登錄的DEMO以及搭建完成了 下面來看看頁面的效果
在初次訪問的時候
圖1
登陸成功之后
圖2
圖3
注意
寫SsoApprovalEndPoint與SsoSpelView目的是去掉登陸之后授權(quán)的效果如果不寫這2個類
在初次訪問的登陸成功之后是有一步授權(quán)的操作的
比如說圖1操作成功之后
點(diǎn)擊Authorize才會跳轉(zhuǎn)到圖2
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- SpringSecurity+jwt+redis基于數(shù)據(jù)庫登錄認(rèn)證的實(shí)現(xiàn)
- spring security結(jié)合jwt實(shí)現(xiàn)用戶重復(fù)登錄處理
- Spring Security中用JWT退出登錄時遇到的坑
- Spring Boot 2結(jié)合Spring security + JWT實(shí)現(xiàn)微信小程序登錄
- springboot+jwt+springSecurity微信小程序授權(quán)登錄問題
- SpringBoot集成Spring Security用JWT令牌實(shí)現(xiàn)登錄和鑒權(quán)的方法
- SpringSecurity+JWT實(shí)現(xiàn)登錄流程分析
相關(guān)文章
使用SpringBoot中的Schedule定時發(fā)送郵件的方法
在SpringBoot中,你可以使用@Scheduled注解來創(chuàng)建定時任務(wù),@Scheduled注解可以應(yīng)用于方法上,表示這個方法是一個定時任務(wù),可以根據(jù)指定的時間間隔或固定時間執(zhí)行,本文就給大家介紹一下如何使用SpringBoot中的Schedule定時發(fā)送郵件,需要的朋友可以參考下2023-08-08spring batch使用reader讀數(shù)據(jù)的內(nèi)存容量問題詳解
這篇文章主要介紹了spring batch使用reader讀數(shù)據(jù)的內(nèi)存容量問題詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07Springboot引入hibernate配置自動建表并進(jìn)行增刪改查操作
這篇文章主要介紹了Springboot引入hibernate配置自動建表并進(jìn)行增刪改查,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-09-09Spring注解配置AOP導(dǎo)致通知執(zhí)行順序紊亂解決方案
這篇文章主要介紹了Spring注解配置AOP導(dǎo)致通知執(zhí)行順序紊亂解決方案,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-10-10SpringBoot中并發(fā)定時任務(wù)的實(shí)現(xiàn)、動態(tài)定時任務(wù)的實(shí)現(xiàn)(看這一篇就夠了)推薦
這篇文章主要介紹了SpringBoot并發(fā)定時任務(wù)動態(tài)定時任務(wù)實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04