社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
摘要:这篇文章向您介绍了单点登录,可伸缩的身份验证的例子的过程,使用JSON Web令牌(JWT)、Spring Boot、Redis。
一:您将构建3个独立的服务
1.micai-auth-service 身份验证服务:将部署在 localhost:8080
2.micai-resource-service 资源服务(为了简化,我们使用相同的代码基):将部署在localhost:8180 and localhost:8280
二:演示效果如下图:
三:micai-auth-service:身份验证服务
<?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>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
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;
}
}
我们使用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 {
private static final String REDIS_SET_ACTIVE_SUBJECTS = "active-subjects";
/**
* 生成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();*/
String token = builder.compact();
RedisUtil.INSTANCE.sadd(REDIS_SET_ACTIVE_SUBJECTS, subject);
return token;
}
/**
* 解析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();*/
String subject = Jwts.parser().setSigningKey(signingKey).parseClaimsJws(token).getBody().getSubject();
if (!RedisUtil.INSTANCE.sismember(REDIS_SET_ACTIVE_SUBJECTS, subject)) {
return null;
}
return subject;
}
public static void invalidateRelatedTokens(HttpServletRequest httpServletRequest) {
RedisUtil.INSTANCE.srem(REDIS_SET_ACTIVE_SUBJECTS, (String) httpServletRequest.getAttribute("username"));
}
}
src/main/java/com/micai/boss/sso/auth/RedisUtil.java
package com.micai.boss.sso.auth;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* 描述:
* <p>
*
* @author: 赵新国
* @date: 2018/5/21 16:17
*/
public enum RedisUtil {
INSTANCE;
private final JedisPool pool;
RedisUtil() {
pool = new JedisPool(new JedisPoolConfig(), "localhost");
}
/**
* 添加
* @param key
* @param value
*/
public void sadd(String key, String value) {
Jedis jedis = null;
try {
jedis = pool.getResource();
jedis.sadd(key, value);
} finally {
if (jedis != null) {
jedis.close();
}
}
}
/**
* 删除
* @param key
* @param value
*/
public void srem(String key, String value) {
Jedis jedis = null;
try {
jedis = pool.getResource();
jedis.srem(key, value);
} finally {
if (jedis != null) {
jedis.close();
}
}
}
/**
* key存在,返回1,key不存在,返回0
* @param key
* @param value
* @return
*/
public boolean sismember(String key, String value) {
Jedis jedis = null;
try {
jedis = pool.getResource();
return jedis.sismember(key, value);
} finally {
if (jedis != null) {
jedis.close();
}
}
}
}
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;
}
}
<!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>
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);
}
}
四:micai-resource-service 资源服务
<?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>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
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();
}
}
}
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 com.micai.boss.sso.resource.auth.JwtUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
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(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
JwtUtil.invalidateRelatedTokens(httpServletRequest);
CookieUtil.clear(httpServletResponse, jwtTokenCookieName);
return "redirect:/";
}
}
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>
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/ResourceWebApplication.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");*/
registrationBean.addUrlPatterns("/protected-resource", "/logout");
return registrationBean;
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(ResourceWebApplication.class);
}
public static void main(String [] args) {
SpringApplication.run(ResourceWebApplication.class, args);
}
}
Resource Service 1
mvn clean spring-boot:run -Dserver.port=8180
Resource Service 2
mvn clean spring-boot:run -Dserver.port=8280
https://gitee.com/micai/micai-boss.git
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!