0
点赞
收藏
分享

微信扫一扫

Single Sign On (SSO),JSON Web Token (JWT) ,Spring Boot


摘要:这篇文章向您介绍了创建(SSO)单点登录应用的过程,使用JSON Web Token(JWT)和Spring Boot实现的可伸缩认证示例。

一:您将构建3个独立的服务

1.micai-auth-service 身份验证服务:将部署在localhost:8080

2.micai-resource-service 资源服务(为了简化,我们使用相同的代码基):将部署在localhost:8180 and localhost:8280

二:演示效果如下图:

Single Sign On (SSO),JSON Web Token (JWT) ,Spring Boot_Spring Boot

三:micai-auth-service:身份验证服务

Single Sign On (SSO),JSON Web Token (JWT) ,Spring Boot_SSO_02

Project dependencies

​​pom.xml​​

<?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>micai-auth</artifactId>
<groupId>com.micai.boss</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>micai-auth-service</artifactId>

<name>micai-auth-service</name>
<url>http://maven.apache.org</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.6.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

CookieUtil

JWT令牌将被保存并从浏览器cookie中提取。

​​src/main/java/com/boss/sso/auth/CookieUtil.java​​

package com.micai.boss.sso.auth;

import org.springframework.web.util.WebUtils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* 描述:
* <p>
*
* @author: 赵新国
* @date: 2018/5/21 11:00
*/
public class CookieUtil {

public static void create(HttpServletResponse httpServletResponse, String name, String value, Boolean secure, Integer maxAge, String domain) {
Cookie cookie = new Cookie(name, value);
cookie.setSecure(secure);// secure=true => work on HTTPS only.
cookie.setHttpOnly(true);// invisible to JavaScript
cookie.setMaxAge(maxAge);// maxAge=0: expire cookie now, maxAge<0: expire cookiie on browser exit.
cookie.setDomain(domain);// visible to domain only.
cookie.setPath("/");// visible to all paths
httpServletResponse.addCookie(cookie);
}

public static void clear(HttpServletResponse httpServletResponse, String name) {
Cookie cookie = new Cookie(name, null);
cookie.setPath("/");
cookie.setHttpOnly(true);
cookie.setMaxAge(0);
httpServletResponse.addCookie(cookie);
}

public static String getValue(HttpServletRequest httpServletRequest, String name) {
Cookie cookie = WebUtils.getCookie(httpServletRequest, name);
return cookie != null ? cookie.getValue() : null;
}
}

JwtUtil

我们使用JWT来生成/解析JWT令牌.

​​src/main/java/com/boss/sso/auth/JwtUtil.java​​

package com.micai.boss.sso.auth;

import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

/**
* 描述:We use JWT to generate/parse JWT Token.
* <p>
*
* @author: 赵新国
* @date: 2018/5/21 11:01
*/
public class JwtUtil {

/**
* 生成Token
* @param signingKey
* @param subject
* @return
*/
public static String generateToken(String signingKey, String subject) {
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
JwtBuilder builder = Jwts.builder()
.setSubject(subject)
.setIssuedAt(now)
.signWith(SignatureAlgorithm.HS256, signingKey);
return builder.compact();
}

/**
* 解析Token
* @param httpServletRequest
* @param jwtTokenCookieName
* @param signingKey
* @return
*/
public static String getSubject(HttpServletRequest httpServletRequest, String jwtTokenCookieName, String signingKey){
String token = CookieUtil.getValue(httpServletRequest, jwtTokenCookieName);
if(token == null) {
return null;
}
return Jwts.parser().setSigningKey(signingKey).parseClaimsJws(token).getBody().getSubject();
}
}

LoginController

​​src/main/java/com/boss/sso/controller/LoginController.java​​

package com.micai.boss.sso.controller;

import com.micai.boss.sso.auth.CookieUtil;
import com.micai.boss.sso.auth.JwtUtil;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/**
* 描述:
* <p>
*
* @author: 赵新国
* @date: 2018/5/21 11:03
*/
@Controller
public class LoginController {

private static final String jwtTokenCookieName = "JWT-TOKEN";
// 签名key
private static final String signingKey = "signingKey";

private static final Map<String, String> credentials = new HashMap<>();

// 构造器初始化账号信息
public LoginController() {
credentials.put("admin", "admin");
}

@RequestMapping("/")
public String home() {
return "login";
}

@RequestMapping("/login")
public String login() {
return "login";
}

@RequestMapping(value = "login", method = RequestMethod.POST)
public String login(HttpServletResponse httpServletResponse, String username, String password, String redirect, Model model) {
if (username == null || !credentials.containsKey(username) || !credentials.get(username).equals(password)) {
model.addAttribute("error", "Invalid username or password!");
return "login";
}
String token = JwtUtil.generateToken(signingKey, username);
CookieUtil.create(httpServletResponse, jwtTokenCookieName, token, false, -1, "localhost");
return "redirect:" + redirect;
}

}

为了简化,我们使用HashMap(凭证)作为用户数据库.


View Template

​​src/main/webapp/login.ftl​​

<!DOCTYPE html>
<html lang="en">
<head>
<title>Authentication Service</title>
</head>
<body>
<form method="POST" action="/login?redirect=${RequestParameters.redirect!}">
<h2>Log in</h2>
<input name="username" type="text" placeholder="Username" autofocus="true"/>
<input name="password" type="password" placeholder="Password"/>
<div>(try username=admin and password=admin)</div>
<div style="color: red">${error!}</div>
<br/>
<button type="submit">Log In</button>
</form>
</body>
</html>

Application Configuration

​​src/main/resources/application.properties​​

spring.freemarker.template-loader-path: /
spring.freemarker.suffix: .ftl

​​src/main/java/com/micai/boss/sso/AuthWebApplication.java​​

package com.micai.boss.sso;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;

/**
* 描述:认证服务启动类
* <p>
*
* @author: 赵新国
* @date: 2018/5/21 11:12
*/
@SpringBootApplication
public class AuthWebApplication extends SpringBootServletInitializer {

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(AuthWebApplication.class);
}

public static void main(String [] args) {
SpringApplication.run(AuthWebApplication.class, args);
}
}

Run

mvn clean spring-boot:run


四:micai-resource-service 资源服务

Single Sign On (SSO),JSON Web Token (JWT) ,Spring Boot_Auth_03

Project dependencies

​​pom.xml​​

<?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>micai-auth</artifactId>
<groupId>com.micai.boss</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>micai-resource-service</artifactId>

<name>micai-resource-service</name>
<url>http://maven.apache.org</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.6.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

JwtFilter

JwtFilter执行SSO。如果JWT令牌不存在(未经身份验证),则重定向到身份验证服务。如果JWT令牌存在(经过身份验证),则提取用户标识并转发请求。

​​src/main/java/com/micai/boss/sso/resource/filter/JwtFilter.java​​

package com.micai.boss.sso.resource.filter;

import com.micai.boss.sso.resource.auth.JwtUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* 描述:JwtFilter enforces SSO. If JWT Token's not existed (unauthenticated), redirects to Authentication Service. If JWT Token's existed (authenticated), extracts user identity and forwards the request.
* <p>
*
* @author: 赵新国
* @date: 2018/5/21 11:29
*/
@Component
public class JwtFilter extends OncePerRequestFilter {

private static final String jwtTokenCookieName = "JWT-TOKEN";
// 签名key
private static final String signingKey = "signingKey";

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
try {
String username = JwtUtil.getSubject(request, jwtTokenCookieName, signingKey);
if (username == null) {
String authService = this.getFilterConfig().getInitParameter("services.auth");
response.sendRedirect(authService + "?redirect=" + request.getRequestURL());
} else {
request.setAttribute("username", username);
filterChain.doFilter(request, response);
}
} catch (IOException e) {
e.printStackTrace();
} catch (ServletException e) {
e.printStackTrace();
}
}
}

ResourceController

​​src/main/java/com/micai/boss/sso/resource/ResourceController.java​​

package com.micai.boss.sso.resource.controller;

import com.micai.boss.sso.resource.auth.CookieUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletResponse;

/**
* 描述:
* <p>
*
* @author: 赵新国
* @date: 2018/5/21 11:35
*/
@Controller
public class ResourceController {

private static final String jwtTokenCookieName = "JWT-TOKEN";

@RequestMapping("/")
public String home() {
return "redirect:/protected-resource";
}

@RequestMapping("/protected-resource")
public String protectedResource() {
return "protected-resource";
}

@RequestMapping("/logout")
public String logout(HttpServletResponse httpServletResponse) {
CookieUtil.clear(httpServletResponse, jwtTokenCookieName);
return "redirect:/";
}

}

View Template

​​src/main/webapp/protected-resource.ftl​​

<!DOCTYPE html>
<html lang="en">
<head>
<title>Protected Resource Service</title>
</head>
<body>
<h2>Hello, ${Request.username!}</h2>
<a href="/logout">Logout</a>
</body>
</html>

Application Configuration

​​src/main/resources/application.properties​​

spring.freemarker.template-loader-path: /
spring.freemarker.suffix: .ftl

# Sso Login Url
services.auth=http://localhost:8080/login

​​src/main/java/com/micai/boss/sso/resource/Resource​​​​WebApplication.java​​

package com.micai.boss.sso.resource;

import com.micai.boss.sso.resource.filter.JwtFilter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import java.util.Collections;

/**
* 描述:资源服务启动类
* <p>
*
* @author: 赵新国
* @date: 2018/5/21 11:38
*/
@SpringBootApplication
public class ResourceWebApplication extends SpringBootServletInitializer {

@Value("${services.auth}")
private String authService;

@Bean
public FilterRegistrationBean jwtFilter() {
final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new JwtFilter());
registrationBean.setInitParameters(Collections.singletonMap("services.auth", authService));
registrationBean.addUrlPatterns("/protected-resource");
return registrationBean;
}

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(ResourceWebApplication.class);
}

public static void main(String [] args) {
SpringApplication.run(ResourceWebApplication.class, args);
}
}

Run

Resource Service 1

mvn clean spring-boot:run -Dserver.port=8180

Resource Service 2

mvn clean spring-boot:run -Dserver.port=8280


Source code

​​https://gitee.com/micai/micai-boss.git​​


举报

相关推荐

0 条评论