Single Sign On (SSO),JSON Web Token (JWT) ,Spring Boot,Redis - Go语言中文社区

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


摘要:这篇文章向您介绍了单点登录,可伸缩的身份验证的例子的过程,使用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:身份验证服务


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>
        <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>

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 {

    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"));
    }
}

RedisUtil

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();
            }
        }
    }
}

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 资源服务


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>
        <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

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 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:/";
    }

}

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/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);
    }
}

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


版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/sxdtzhaoxinguo/article/details/80394174
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-03-07 22:24:07
  • 阅读 ( 877 )
  • 分类:前端

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢