SpringBoot+JWT實(shí)現(xiàn)單點(diǎn)登錄完美解決方案
一、什么是單點(diǎn)登錄?
單點(diǎn)登錄是一種統(tǒng)一認(rèn)證和授權(quán)機(jī)制,指在多個(gè)應(yīng)用系統(tǒng)中,用戶只需要登錄一次就可以訪問所有相互信任的系統(tǒng),不需要重新登錄驗(yàn)證。
單點(diǎn)登錄一般用于互相授信的系統(tǒng),實(shí)現(xiàn)單一位置登錄,其他信任的應(yīng)用直接免登錄的方式,在多個(gè)應(yīng)用系統(tǒng)中,只需要登錄一次,就可以訪問其他互相信任的應(yīng)用系統(tǒng)。
隨著時(shí)代的演進(jìn),大型web系統(tǒng)早已從單體應(yīng)用架構(gòu)發(fā)展為如今的多系統(tǒng)分布式應(yīng)用群。但無論系統(tǒng)內(nèi)部多么復(fù)雜,對用戶而言,都是一個(gè)統(tǒng)一的整體,訪問web系統(tǒng)的整個(gè)應(yīng)用群要和訪問單個(gè)系統(tǒng)一樣,登錄/注銷只要一次就夠了,不可能讓一個(gè)用戶在每個(gè)業(yè)務(wù)系統(tǒng)上都進(jìn)行一次登錄驗(yàn)證操作,這時(shí)就需要獨(dú)立出一個(gè)單獨(dú)的認(rèn)證系統(tǒng),它就是單點(diǎn)登錄系統(tǒng)。
二、單點(diǎn)登錄的優(yōu)點(diǎn)
1.方便用戶使用。用戶不需要多次登錄系統(tǒng),不需要記住多個(gè)密碼,方便用戶操作。
2.提高開發(fā)效率。單點(diǎn)登錄為開發(fā)人員提供類一個(gè)通用的驗(yàn)證框架。
3.簡化管理。如果在應(yīng)用程序中加入了單點(diǎn)登錄的協(xié)議,管理用戶賬戶的負(fù)擔(dān)就會(huì)減輕。
三、JWT 機(jī)制
JWT(JSON Web Token)它將用戶信息加密到token里,服務(wù)器不保存任何用戶信息。服務(wù)器通過使用保存的密鑰驗(yàn)證JWTToken的正確性,只要正確就通過驗(yàn)證。
數(shù)據(jù)結(jié)構(gòu):
JWT包含三個(gè)部分:Header頭部,Payload負(fù)載和Signature簽名。三個(gè)部門用“.”分割。校驗(yàn)也是JWT內(nèi)部自己實(shí)現(xiàn)的 ,并且可以將你存儲(chǔ)時(shí)候的信息從token中取出來無須查庫。
JWT執(zhí)行流程:
JWT的請求流程也特別簡單,首先使用賬號(hào)登錄獲取Token,然后后面的各種請求,都帶上這個(gè)Token即可。具體流程如下:
1. 客戶端發(fā)起登錄請求,傳入賬號(hào)密碼;
2. 服務(wù)端使用私鑰創(chuàng)建一個(gè)Token;
3. 服務(wù)器返回Token給客戶端;
4. 客戶端向服務(wù)端發(fā)送請求,在請求頭中攜帶Token;
5. 服務(wù)器驗(yàn)證該Token;
6. 返回結(jié)果。
四.創(chuàng)建Maven父項(xiàng)目
2.指定打包類型為pom
五.創(chuàng)建認(rèn)證模塊sso
1.添加依賴,完整的pom文件如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.13</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>sso</artifactId> <version>0.0.1-SNAPSHOT</version> <name>sso</name> <description>sso</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!--jwt--> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.8.2</version> </dependency> <!--thymeleaf--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2添加jwt相關(guān)配置
3.創(chuàng)建JWT配置類和JWT工具類
示例代碼如下:
package com.example.sso.bean; import lombok.Getter; import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * @author qx * @date 2023/7/4 * @des Jwt配置類 */ @Component @ConfigurationProperties(prefix = "jwt") @Getter @Setter public class JwtProperties { /** * 過期時(shí)間-分鐘 */ private Integer expireTime; /** * 密鑰 */ private String secret; }
package com.example.sso.util; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.example.sso.bean.JwtProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Date; /** * @author qx * @date 2023/7/4 * @des JWT工具類 */ @Component public class JwtUtil { @Autowired private JwtProperties jwtProperties; /** * 生成一個(gè)jwt字符串 * * @param username 用戶名 * @return jwt字符串 */ public String sign(String username) { Algorithm algorithm = Algorithm.HMAC256(jwtProperties.getSecret()); return JWT.create() // 設(shè)置過期時(shí)間1個(gè)小時(shí) .withExpiresAt(new Date(System.currentTimeMillis() + jwtProperties.getExpireTime() * 60 * 1000)) // 設(shè)置負(fù)載 .withClaim("username", username).sign(algorithm); } public static void main(String[] args) { Algorithm algorithm = Algorithm.HMAC256("KU5TjMO6zmh03bU3"); String username = "admin"; String token = JWT.create() // 設(shè)置過期時(shí)間1個(gè)小時(shí) .withExpiresAt(new Date(System.currentTimeMillis() + 60 * 60 * 1000)) // 設(shè)置負(fù)載 .withClaim("username", username).sign(algorithm); System.out.println(token); } /** * 校驗(yàn)token是否正確 * * @param token token值 */ public boolean verify(String token) { if (token == null || token.length() == 0) { throw new RuntimeException("token為空"); } try { Algorithm algorithm = Algorithm.HMAC256(jwtProperties.getSecret()); JWTVerifier jwtVerifier = JWT.require(algorithm).build(); jwtVerifier.verify(token); return true; } catch (Exception e) { e.printStackTrace(); return false; } } }
4.創(chuàng)建服務(wù)層
package com.example.sso.service; import com.example.sso.util.JwtUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @author qx * @date 2023/7/4 * @des 登錄服務(wù)層 */ @Service public class LoginService { @Autowired private JwtUtil jwtUtil; /** * 登錄 * * @param username 用戶名 * @param password 密碼 * @return token值 */ public String login(String username, String password) { if ("".equals(username) || "".equals(password)) { throw new RuntimeException("用戶名或密碼不能為空"); } // 為了測試方便 不去數(shù)據(jù)庫比較密碼 if ("123".equals(password)) { // 返回生成的token return jwtUtil.sign(username); } return null; } /** * 校驗(yàn)jwt是否成功 * * @param token token值 * @return 校驗(yàn)是否超過 */ public boolean checkJwt(String token) { return jwtUtil.verify(token); } }
5.創(chuàng)建控制層
package com.example.sso.controller; import com.example.sso.service.LoginService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; /** * @author qx * @date 2023/7/4 * @des 驗(yàn)證控制層 */ @Controller @RequestMapping("/sso") public class AuthController { @Autowired private LoginService loginService; /** * 登錄頁面 */ @GetMapping("/login") public String toLogin() { return "login"; } /** * 登錄 * * @param username 用戶名 * @param password 密碼 * @return token值 */ @PostMapping("/login") @ResponseBody public String login(String username, String password) { return loginService.login(username, password); } /** * 驗(yàn)證jwt * * @param token token * @return 驗(yàn)證jwt是否合法 */ @RequestMapping("/checkJwt") @ResponseBody public boolean checkJwt(String token) { return loginService.checkJwt(token); } }
6.創(chuàng)建一個(gè)登錄頁面login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登錄</title> </head> <body> <form method="post" action="/sso/login"> 用戶名:<input type="text" name="username"/><br/> 密碼:<input type="password" name="password"/><br/> <button type="submit">登錄</button> </form> </body> </html>
六、創(chuàng)建應(yīng)用系統(tǒng)projectA
1.項(xiàng)目pom文件如下所示
<?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> <parent> <groupId>org.example</groupId> <artifactId>my-sso</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>projectA</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.7.13</version> </dependency> <!--okhttp--> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.10.0</version> </dependency> </dependencies> </project>
2.修改配置文件
3.創(chuàng)建過濾器
package com.example.projectA.filter; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author qx * @date 2023/7/4 * @des 登錄過濾器 */ @Component @WebFilter(urlPatterns = "/**") public class LoginFilter implements Filter { @Value("${sso_server}") private String serverHost; @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; String token = httpServletRequest.getParameter("token"); if (this.check(token)) { filterChain.doFilter(servletRequest, servletResponse); } else { HttpServletResponse response = (HttpServletResponse) servletResponse; String redirect = serverHost + "/login"; response.sendRedirect(redirect); } } /** * 驗(yàn)證token * * @param token * @return * @throws IOException */ private boolean check(String token) throws IOException { if (token == null || token.trim().length() == 0) { return false; } OkHttpClient client = new OkHttpClient(); // 請求驗(yàn)證token的合法性 String url = serverHost + "/checkJwt?token=" + token; Request request = new Request.Builder().url(url).build(); Response response = client.newCall(request).execute(); return Boolean.parseBoolean(response.body().string()); } }
4.創(chuàng)建測試控制層
package com.example.projectA.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * @author qx * @date 2023/7/4 * @des 測試A */ @RestController public class IndexController { @GetMapping("/testA") public String testA() { return "輸出testA"; } }
5.啟動(dòng)類
package com.example.projectA; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.ServletComponentScan; /** * @author qx * @date 2023/7/4 * @des Projecta啟動(dòng)類 */ @SpringBootApplication @ServletComponentScan public class ProjectaApplication { public static void main(String[] args) { SpringApplication.run(ProjectaApplication.class,args); } }
6.啟動(dòng)項(xiàng)目測試
我們訪問http://localhost:8081/testA 系統(tǒng)跳轉(zhuǎn)到了驗(yàn)證模塊的登錄頁面
我們輸入賬號(hào)密碼登錄成功后返回token
如何我們復(fù)制這段數(shù)據(jù),把數(shù)據(jù)傳遞到token參數(shù)。
我們看到正確獲取到了數(shù)據(jù)。
七、創(chuàng)建應(yīng)用系統(tǒng)projectB
我們再次模仿projectA創(chuàng)建projectB子模塊。
啟動(dòng)模塊B
我們直接測試帶上token參數(shù)
通過之前的token,無需登錄即可成功進(jìn)入了應(yīng)用系統(tǒng)B。說明我們的單點(diǎn)登錄系統(tǒng)搭建成功。
到此這篇關(guān)于SpringBoot+JWT實(shí)現(xiàn)單點(diǎn)登錄解決方案的文章就介紹到這了,更多相關(guān)SpringBoot JWT單點(diǎn)登錄內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JAVA實(shí)現(xiàn)利用第三方平臺(tái)發(fā)送短信驗(yàn)證碼
本文以注冊為例,在SpringMVC+Spring+Mybatis框架的基礎(chǔ)上完成該短信驗(yàn)證碼功能。需要的朋友一起來看下吧2016-12-12Java利用Easyexcel導(dǎo)出excel表格的示例代碼
這篇文章主要為大家詳細(xì)介紹了Java利用Easyexcel導(dǎo)出excel表格的示例代碼,文中的代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下2022-07-07Apache Calcite進(jìn)行SQL解析(java代碼實(shí)例)
Calcite是一款開源SQL解析工具, 可以將各種SQL語句解析成抽象語法樹AST(Abstract Syntax Tree), 之后通過操作AST就可以把SQL中所要表達(dá)的算法與關(guān)系體現(xiàn)在具體代碼之中,今天通過代碼實(shí)例給大家介紹Apache Calcite進(jìn)行SQL解析問題,感興趣的朋友一起看看吧2022-01-01java Disruptor構(gòu)建高性能內(nèi)存隊(duì)列使用詳解
這篇文章主要為大家介紹了java Disruptor構(gòu)建高性能內(nèi)存隊(duì)列使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12spring AOP的After增強(qiáng)實(shí)現(xiàn)方法實(shí)例分析
這篇文章主要介紹了spring AOP的After增強(qiáng)實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了spring面向切面AOP的After增強(qiáng)實(shí)現(xiàn)步驟與相關(guān)操作技巧,需要的朋友可以參考下2020-01-01java根據(jù)方法名稱取得反射方法的參數(shù)類型示例
利用java反射原理調(diào)用方法時(shí),常先需要傳入方法參數(shù)數(shù)組才能取得方法。該方法參數(shù)數(shù)組采用動(dòng)態(tài)取得的方式比較合適2014-02-02