关于spring-security:SpringBootSpringSecurityJWT整合实现单点登录SSO史上最全详解

34次阅读

共计 29000 个字符,预计需要花费 73 分钟才能阅读完成。

欢送微信搜寻公众号【java 版 web 我的项目】获取资源:java 学习视频 / 设计模式笔记 / 算法手册 /java 我的项目

一、什么是单点登陆

  单点登录(Single Sign On),简称为 SSO,是目前比拟风行的企业业务整合的解决方案之一。SSO 的定义是在多个利用零碎中,用户只须要登录一次就能够拜访所有相互信任的利用零碎

二、简略的运行机制

  单点登录的机制其实是比较简单的,用一个事实中的例子做比拟。某公园外部有许多独立的景点,游客能够在各个景点门口独自买票。对于须要玩耍所有的景点的游客,这种买票形式很不不便,须要在每个景点门口排队买票,钱包拿 进拿出的,容易失落,很不平安。于是绝大多数游客抉择在大门口买一张通票(也叫套票),就能够玩遍所有的景点而不须要从新再买票。他们只须要在每个景点门 口出示一下方才买的套票就可能被容许进入每个独立的景点。
单点登录的机制也一样,如下图所示,

用户认证 :这一环节次要是用户向认证服务器发动认证申请,认证服务器给用户返回一个胜利的令牌 token,次要在认证服务器中实现,即图中的认证零碎,留神认证零碎只能有一个。
身份校验:这一环节是用户携带 token 去拜访其余服务器时,在其余服务器中要对 token 的真伪进行测验,次要在资源服务器中实现,即图中的利用零碎 2 3

三、JWT 介绍

概念阐明

  从分布式认证流程中,咱们不难发现,这两头起最关键作用的就是 token,token 的平安与否,间接关系到零碎的健壮性,这里咱们抉择应用 JWT 来实现 token 的生成和校验。
  JWT,全称JSON Web Token,官网地址 https://jwt.io,是一款杰出的分布式身份校验计划。能够生成 token,也能够解析测验 token。

JWT 生成的 token 由三局部组成:

头部 :次要设置一些标准信息,签名局部的编码格局就在头部中申明。
载荷 :token 中寄存无效信息的局部,比方用户名,用户角色,过期工夫等,然而不要放明码,会泄露!
签名:将头部与载荷别离采纳 base64 编码后,用“.”相连,再退出盐,最初应用头部申明的编码类型进行编码,就失去了签名。

JWT 生成 token 的安全性剖析

  从 JWT 生成的 token 组成上来看,要想防止 token 被伪造,次要就得看签名局部了,而签名局部又有三局部组成,其中头部和载荷的 base64 编码,简直是通明的,毫无安全性可言,那么最终守护 token 平安的重任就落在了退出的 下面了!试想:如果生成 token 所用的盐与解析 token 时退出的盐是一样的。岂不是相似于中国人民银行把人民币防伪技术公开了?大家能够用这个盐来解析 token,就能用来伪造 token。这时,咱们就须要对盐采纳 非对称加密 的形式进行加密,以达到生成 token 与校验 token 方所用的盐不统一的平安成果!

非对称加密 RSA 介绍

基本原理 :同时生成两把密钥:私钥和公钥,私钥隐秘保留,公钥能够下发给信赖客户端
私钥加密 ,持有私钥或公钥才能够解密
公钥加密 ,持有私钥才可解密
长处:平安,难以破解
毛病:算法比拟耗时,为了平安,能够承受
历史:三位数学家 Rivest、Shamir 和 Adleman 设计了一种算法,能够实现非对称加密。这种算法用他们三个人的名字缩写:RSA。

四、SpringSecurity 整合 JWT

1. 认证思路剖析

  SpringSecurity 次要是通过过滤器来实现性能的!咱们要找到 SpringSecurity 实现认证和校验身份的过滤器!

回顾集中式认证流程

用户认证
  应用UsernamePasswordAuthenticationFilter 过滤器中 attemptAuthentication 办法实现认证性能,该过滤器父类中 successfulAuthentication 办法实现认证胜利后的操作。
身份校验
  应用BasicAuthenticationFilter 过滤器中 doFilterInternal 办法验证是否登录,以决定是否进入后续过滤器。

剖析分布式认证流程

用户认证
  因为分布式我的项目,少数是前后端拆散的架构设计,咱们要满足能够承受异步 post 的认证申请参数,须要批改 UsernamePasswordAuthenticationFilter 过滤器中 attemptAuthentication 办法,让其可能接管申请体。
  另外,默认 successfulAuthentication 办法在认证通过后,是把用户信息间接放入 session 就完事了,当初咱们须要批改这个办法,在认证通过后生成 token 并返回给用户。
身份校验
  原来 BasicAuthenticationFilter 过滤器中 doFilterInternal 办法校验用户是否登录,就是看 session 中是否有用户信息,咱们要批改为,验证用户携带的 token 是否非法,并解析出用户信息,交给 SpringSecurity,以便于后续的受权性能能够失常应用。

2. 具体实现

  为了演示单点登录的成果,咱们设计如下我的项目构造

2.1 父工程创立

  因为本案例须要创立多个零碎,所以咱们应用 maven 聚合工程来实现,首先创立一个父工程,导入 springboot 的父依赖即可

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.3.RELEASE</version>
    <relativePath/>
</parent>
123456

2.2 公共工程创立

  而后创立一个 common 工程,其余工程依赖此零碎

导入 JWT 相干的依赖

<dependencies>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>0.10.7</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-impl</artifactId>
        <version>0.10.7</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-jackson</artifactId>
        <version>0.10.7</version>
        <scope>runtime</scope>
    </dependency>
    <!--jackson 包 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.9</version>
    </dependency>
    <!-- 日志包 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-logging</artifactId>
    </dependency>
    <dependency>
        <groupId>joda-time</groupId>
        <artifactId>joda-time</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
</dependencies>
123456789101112131415161718192021222324252627282930313233343536373839404142

创立相干的工具类

Payload

/**
 * @program: springboot-54-security-jwt-demo
 * @description:
 * @author: 波波烤鸭
 * @create: 2019-12-03 10:28
 */
@Data
public class Payload <T>{
    private String id;
    private T userInfo;
    private Date expiration;
}
123456789101112

JsonUtils

package com.dpb.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.List;
import java.util.Map;

/**
 * @author: 波波烤鸭
 **/
public class JsonUtils {public static final ObjectMapper mapper = new ObjectMapper();

    private static final Logger logger = LoggerFactory.getLogger(JsonUtils.class);

    public static String toString(Object obj) {if (obj == null) {return null;}
        if (obj.getClass() == String.class) {return (String) obj;
        }
        try {return mapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {logger.error("json 序列化出错:" + obj, e);
            return null;
        }
    }

    public static <T> T toBean(String json, Class<T> tClass) {
        try {return mapper.readValue(json, tClass);
        } catch (IOException e) {logger.error("json 解析出错:" + json, e);
            return null;
        }
    }

    public static <E> List<E> toList(String json, Class<E> eClass) {
        try {return mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, eClass));
        } catch (IOException e) {logger.error("json 解析出错:" + json, e);
            return null;
        }
    }

    public static <K, V> Map<K, V> toMap(String json, Class<K> kClass, Class<V> vClass) {
        try {return mapper.readValue(json, mapper.getTypeFactory().constructMapType(Map.class, kClass, vClass));
        } catch (IOException e) {logger.error("json 解析出错:" + json, e);
            return null;
        }
    }

    public static <T> T nativeRead(String json, TypeReference<T> type) {
        try {return mapper.readValue(json, type);
        } catch (IOException e) {logger.error("json 解析出错:" + json, e);
            return null;
        }
    }
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172

JwtUtils

package com.dpb.utils;

import com.dpb.domain.Payload;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.joda.time.DateTime;

import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Base64;
import java.util.UUID;

/**
 * @author: 波波烤鸭
 * 生成 token 以及校验 token 相干办法
 */
public class JwtUtils {

    private static final String JWT_PAYLOAD_USER_KEY = "user";

    /**
     * 私钥加密 token
     *
     * @param userInfo   载荷中的数据
     * @param privateKey 私钥
     * @param expire     过期工夫,单位分钟
     * @return JWT
     */
    public static String generateTokenExpireInMinutes(Object userInfo, PrivateKey privateKey, int expire) {return Jwts.builder()
                .claim(JWT_PAYLOAD_USER_KEY, JsonUtils.toString(userInfo))
                .setId(createJTI())
                .setExpiration(DateTime.now().plusMinutes(expire).toDate())
                .signWith(privateKey, SignatureAlgorithm.RS256)
                .compact();}

    /**
     * 私钥加密 token
     *
     * @param userInfo   载荷中的数据
     * @param privateKey 私钥
     * @param expire     过期工夫,单位秒
     * @return JWT
     */
    public static String generateTokenExpireInSeconds(Object userInfo, PrivateKey privateKey, int expire) {return Jwts.builder()
                .claim(JWT_PAYLOAD_USER_KEY, JsonUtils.toString(userInfo))
                .setId(createJTI())
                .setExpiration(DateTime.now().plusSeconds(expire).toDate())
                .signWith(privateKey, SignatureAlgorithm.RS256)
                .compact();}

    /**
     * 公钥解析 token
     *
     * @param token     用户申请中的 token
     * @param publicKey 公钥
     * @return Jws<Claims>
     */
    private static Jws<Claims> parserToken(String token, PublicKey publicKey) {return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
    }

    private static String createJTI() {return new String(Base64.getEncoder().encode(UUID.randomUUID().toString().getBytes()));
    }

    /**
     * 获取 token 中的用户信息
     *
     * @param token     用户申请中的令牌
     * @param publicKey 公钥
     * @return 用户信息
     */
    public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey, Class<T> userType) {Jws<Claims> claimsJws = parserToken(token, publicKey);
        Claims body = claimsJws.getBody();
        Payload<T> claims = new Payload<>();
        claims.setId(body.getId());
        claims.setUserInfo(JsonUtils.toBean(body.get(JWT_PAYLOAD_USER_KEY).toString(), userType));
        claims.setExpiration(body.getExpiration());
        return claims;
    }

    /**
     * 获取 token 中的载荷信息
     *
     * @param token     用户申请中的令牌
     * @param publicKey 公钥
     * @return 用户信息
     */
    public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey) {Jws<Claims> claimsJws = parserToken(token, publicKey);
        Claims body = claimsJws.getBody();
        Payload<T> claims = new Payload<>();
        claims.setId(body.getId());
        claims.setExpiration(body.getExpiration());
        return claims;
    }
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104

RsaUtils

package com.dpb.utils;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * @author 波波烤鸭
 */
public class RsaUtils {

    private static final int DEFAULT_KEY_SIZE = 2048;
    /**
     * 从文件中读取公钥
     *
     * @param filename 公钥保留门路,绝对于 classpath
     * @return 公钥对象
     * @throws Exception
     */
    public static PublicKey getPublicKey(String filename) throws Exception {byte[] bytes = readFile(filename);
        return getPublicKey(bytes);
    }

    /**
     * 从文件中读取密钥
     *
     * @param filename 私钥保留门路,绝对于 classpath
     * @return 私钥对象
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(String filename) throws Exception {byte[] bytes = readFile(filename);
        return getPrivateKey(bytes);
    }

    /**
     * 获取公钥
     *
     * @param bytes 公钥的字节模式
     * @return
     * @throws Exception
     */
    private static PublicKey getPublicKey(byte[] bytes) throws Exception {bytes = Base64.getDecoder().decode(bytes);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePublic(spec);
    }

    /**
     * 获取密钥
     *
     * @param bytes 私钥的字节模式
     * @return
     * @throws Exception
     */
    private static PrivateKey getPrivateKey(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeySpecException {bytes = Base64.getDecoder().decode(bytes);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePrivate(spec);
    }

    /**
     * 依据密文,生存 rsa 公钥和私钥, 并写入指定文件
     *
     * @param publicKeyFilename  公钥文件门路
     * @param privateKeyFilename 私钥文件门路
     * @param secret             生成密钥的密文
     */
    public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret, int keySize) throws Exception {KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        SecureRandom secureRandom = new SecureRandom(secret.getBytes());
        keyPairGenerator.initialize(Math.max(keySize, DEFAULT_KEY_SIZE), secureRandom);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        // 获取公钥并写出
        byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
        publicKeyBytes = Base64.getEncoder().encode(publicKeyBytes);
        writeFile(publicKeyFilename, publicKeyBytes);
        // 获取私钥并写出
        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
        privateKeyBytes = Base64.getEncoder().encode(privateKeyBytes);
        writeFile(privateKeyFilename, privateKeyBytes);
    }

    private static byte[] readFile(String fileName) throws Exception {return Files.readAllBytes(new File(fileName).toPath());
    }

    private static void writeFile(String destPath, byte[] bytes) throws IOException {File dest = new File(destPath);
        if (!dest.exists()) {dest.createNewFile();
        }
        Files.write(dest.toPath(), bytes);
    }
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103

在通用子模块中编写测试类生成 rsa 公钥和私钥

/**
 * @program: springboot-54-security-jwt-demo
 * @description:
 * @author: 波波烤鸭
 * @create: 2019-12-03 11:08
 */
public class JwtTest {
    private String privateKey = "c:/tools/auth_key/id_key_rsa";

    private String publicKey = "c:/tools/auth_key/id_key_rsa.pub";

    @Test
    public void test1() throws Exception{RsaUtils.generateKey(publicKey,privateKey,"dpb",1024);
    }

}
1234567891011121314151617

2.3 认证零碎创立

  接下来咱们创立咱们的认证服务。

导入相干的依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <artifactId>security-jwt-common</artifactId>
        <groupId>com.dpb</groupId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.0</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.10</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>
1234567891011121314151617181920212223242526272829303132333435

创立配置文件

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/srm
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
mybatis:
  type-aliases-package: com.dpb.domain
  mapper-locations: classpath:mapper/*.xml
logging:
  level:
    com.dpb: debug
rsa:
  key:
    pubKeyFile: c:\tools\auth_key\id_key_rsa.pub
    priKeyFile: c:\tools\auth_key\id_key_rsa
1234567891011121314151617

提供公钥私钥的配置类

package com.dpb.config;

import com.dpb.utils.RsaUtils;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.security.PrivateKey;
import java.security.PublicKey;

/**
 * @program: springboot-54-security-jwt-demo
 * @description:
 * @author: 波波烤鸭
 * @create: 2019-12-03 11:25
 */
@Data
@ConfigurationProperties(prefix = "rsa.key")
public class RsaKeyProperties {

    private String pubKeyFile;
    private String priKeyFile;

    private PublicKey publicKey;
    private PrivateKey privateKey;

    /**
     * 系统启动的时候触发
     * @throws Exception
     */
    @PostConstruct
    public void createRsaKey() throws Exception {publicKey = RsaUtils.getPublicKey(pubKeyFile);
        privateKey = RsaUtils.getPrivateKey(priKeyFile);
    }

}
1234567891011121314151617181920212223242526272829303132333435363738

创立启动类

/**
 * @program: springboot-54-security-jwt-demo
 * @description: 启动类
 * @author: 波波烤鸭
 * @create: 2019-12-03 11:23
 */
@SpringBootApplication
@MapperScan("com.dpb.mapper")
@EnableConfigurationProperties(RsaKeyProperties.class)
public class App {public static void main(String[] args) {SpringApplication.run(App.class,args);
    }
}
123456789101112131415

实现数据认证的逻辑

pojo

package com.dpb.domain;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;

/**
 * @program: springboot-54-security-jwt-demo
 * @description:
 * @author: 波波烤鸭
 * @create: 2019-12-03 15:21
 */
@Data
public class RolePojo implements GrantedAuthority {

    private Integer id;
    private String roleName;
    private String roleDesc;

    @JsonIgnore
    @Override
    public String getAuthority() {return roleName;}
}
12345678910111213141516171819202122232425
package com.dpb.domain;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * @program: springboot-54-security-jwt-demo
 * @description:
 * @author: 波波烤鸭
 * @create: 2019-12-03 11:33
 */
@Data
public class UserPojo implements UserDetails {

    private Integer id;

    private String username;

    private String password;

    private Integer status;

    private List<RolePojo> roles;

    @JsonIgnore
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {List<SimpleGrantedAuthority> auth = new ArrayList<>();
        auth.add(new SimpleGrantedAuthority("ADMIN"));
        return auth;
    }

    @Override
    public String getPassword() {return this.password;}

    @Override
    public String getUsername() {return this.username;}
    @JsonIgnore
    @Override
    public boolean isAccountNonExpired() {return true;}
    @JsonIgnore
    @Override
    public boolean isAccountNonLocked() {return true;}
    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired() {return true;}
    @JsonIgnore
    @Override
    public boolean isEnabled() {return true;}
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869

Mapper 接口

public interface UserMapper {public UserPojo queryByUserName(@Param("userName") String userName);
}
123

Mapper 映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dpb.mapper.UserMapper">
    <select id="queryByUserName" resultType="UserPojo">
        select * from t_user where username = #{userName}
    </select>
</mapper>
123456789

Service

public interface UserService extends UserDetailsService {

}
123
@Service
@Transactional
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper mapper;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {UserPojo user = mapper.queryByUserName(s);

        return user;
    }
}
1234567891011121314

自定义认证过滤器

package com.dpb.filter;

import com.dpb.config.RsaKeyProperties;
import com.dpb.domain.RolePojo;
import com.dpb.domain.UserPojo;
import com.dpb.utils.JwtUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.bytebuddy.agent.builder.AgentBuilder;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @program: springboot-54-security-jwt-demo
 * @description:
 * @author: 波波烤鸭
 * @create: 2019-12-03 11:57
 */
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;
    private RsaKeyProperties prop;

    public TokenLoginFilter(AuthenticationManager authenticationManager, RsaKeyProperties prop) {
        this.authenticationManager = authenticationManager;
        this.prop = prop;
    }

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {UserPojo sysUser = new ObjectMapper().readValue(request.getInputStream(), UserPojo.class);

            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword());
            return authenticationManager.authenticate(authRequest);
        }catch (Exception e){
            try {response.setContentType("application/json;charset=utf-8");
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                PrintWriter out = response.getWriter();
                Map resultMap = new HashMap();
                resultMap.put("code", HttpServletResponse.SC_UNAUTHORIZED);
                resultMap.put("msg", "用户名或明码谬误!");
                out.write(new ObjectMapper().writeValueAsString(resultMap));
                out.flush();
                out.close();}catch (Exception outEx){outEx.printStackTrace();
            }
            throw new RuntimeException(e);
        }
    }

    public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {UserPojo user = new UserPojo();
        user.setUsername(authResult.getName());
        user.setRoles((List<RolePojo>)authResult.getAuthorities());
        String token = JwtUtils.generateTokenExpireInMinutes(user, prop.getPrivateKey(), 24 * 60);
        response.addHeader("Authorization", "Bearer"+token);
        try {response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_OK);
            PrintWriter out = response.getWriter();
            Map resultMap = new HashMap();
            resultMap.put("code", HttpServletResponse.SC_OK);
            resultMap.put("msg", "认证通过!");
            out.write(new ObjectMapper().writeValueAsString(resultMap));
            out.flush();
            out.close();}catch (Exception outEx){outEx.printStackTrace();
        }
    }
}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889

自定义校验 token 的过滤器

package com.dpb.filter;

import com.dpb.config.RsaKeyProperties;
import com.dpb.domain.Payload;
import com.dpb.domain.UserPojo;
import com.dpb.utils.JwtUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * @program: springboot-54-security-jwt-demo
 * @description:
 * @author: 波波烤鸭
 * @create: 2019-12-03 12:39
 */
public class TokenVerifyFilter  extends BasicAuthenticationFilter {
    private RsaKeyProperties prop;

    public TokenVerifyFilter(AuthenticationManager authenticationManager, RsaKeyProperties prop) {super(authenticationManager);
        this.prop = prop;
    }

    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {String header = request.getHeader("Authorization");
        if (header == null || !header.startsWith("Bearer")) {
            // 如果携带谬误的 token,则给用户提醒请登录!chain.doFilter(request, response);
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            PrintWriter out = response.getWriter();
            Map resultMap = new HashMap();
            resultMap.put("code", HttpServletResponse.SC_FORBIDDEN);
            resultMap.put("msg", "请登录!");
            out.write(new ObjectMapper().writeValueAsString(resultMap));
            out.flush();
            out.close();} else {
            // 如果携带了正确格局的 token 要先失去 token
            String token = header.replace("Bearer", "");
            // 验证 tken 是否正确
            Payload<UserPojo> payload = JwtUtils.getInfoFromToken(token, prop.getPublicKey(), UserPojo.class);
            UserPojo user = payload.getUserInfo();
            if(user!=null){UsernamePasswordAuthenticationToken authResult = new UsernamePasswordAuthenticationToken(user.getUsername(), null, user.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authResult);
                chain.doFilter(request, response);
            }
        }
    }

}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364

\### 编写 SpringSecurity 的配置类

package com.dpb.config;

import com.dpb.filter.TokenLoginFilter;
import com.dpb.filter.TokenVerifyFilter;
import com.dpb.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
 * @program: springboot-54-security-jwt-demo
 * @description:
 * @author: 波波烤鸭
 * @create: 2019-12-03 12:41
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled=true)
public class WebSecurityConfig   extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userService;

    @Autowired
    private RsaKeyProperties prop;

    @Bean
    public BCryptPasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();
    }

    // 指定认证对象的起源
    public void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    }
    //SpringSecurity 配置信息
    public void configure(HttpSecurity http) throws Exception {http.csrf()
                .disable()
                .authorizeRequests()
                .antMatchers("/user/query").hasAnyRole("ADMIN")
                .anyRequest()
                .authenticated()
                .and()
                .addFilter(new TokenLoginFilter(super.authenticationManager(), prop))
                .addFilter(new TokenVerifyFilter(super.authenticationManager(), prop))
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556

启动服务测试

启动服务

通过 Postman 来拜访测试

依据 token 信息咱们拜访其余资源

2.4 资源零碎创立

阐明
资源服务能够有很多个,这里只拿产品服务为例,记住,资源服务中只能通过公钥验证认证。不能签发 token!创立产品服务并导入 jar 包依据理论业务导包即可,咱们就临时和认证服务一样了。

接下来咱们再创立一个资源服务

导入相干的依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <artifactId>security-jwt-common</artifactId>
        <groupId>com.dpb</groupId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.0</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.10</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>
1234567891011121314151617181920212223242526272829303132333435

编写产品服务配置文件

切记这里只能有公钥地址!

server:
  port: 9002
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/srm
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
mybatis:
  type-aliases-package: com.dpb.domain
  mapper-locations: classpath:mapper/*.xml
logging:
  level:
    com.dpb: debug
rsa:
  key:
    pubKeyFile: c:\tools\auth_key\id_key_rsa.pub
123456789101112131415161718

编写读取公钥的配置类

package com.dpb.config;

import com.dpb.utils.RsaUtils;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

import javax.annotation.PostConstruct;
import java.security.PrivateKey;
import java.security.PublicKey;

/**
 * @program: springboot-54-security-jwt-demo
 * @description:
 * @author: 波波烤鸭
 * @create: 2019-12-03 11:25
 */
@Data
@ConfigurationProperties(prefix = "rsa.key")
public class RsaKeyProperties {

    private String pubKeyFile;

    private PublicKey publicKey;

    /**
     * 系统启动的时候触发
     * @throws Exception
     */
    @PostConstruct
    public void createRsaKey() throws Exception {publicKey = RsaUtils.getPublicKey(pubKeyFile);
    }

}
12345678910111213141516171819202122232425262728293031323334

编写启动类

package com.dpb;

import com.dpb.config.RsaKeyProperties;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

/**
 * @program: springboot-54-security-jwt-demo
 * @description:
 * @author: 波波烤鸭
 * @create: 2019-12-03 17:23
 */
@SpringBootApplication
@MapperScan("com.dpb.mapper")
@EnableConfigurationProperties(RsaKeyProperties.class)
public class App {public static void main(String[] args) {SpringApplication.run(App.class,args);
    }
}
1234567891011121314151617181920212223

复制认证服务中,用户对象,角色对象和校验认证的接口

复制认证服务中的相干内容即可

复制认证服务中 SpringSecurity 配置类做批改

package com.dpb.config;

import com.dpb.filter.TokenVerifyFilter;
import com.dpb.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
 * @program: springboot-54-security-jwt-demo
 * @description:
 * @author: 波波烤鸭
 * @create: 2019-12-03 12:41
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled=true)
public class WebSecurityConfig   extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userService;

    @Autowired
    private RsaKeyProperties prop;

    @Bean
    public BCryptPasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();
    }

    // 指定认证对象的起源
    public void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    }
    //SpringSecurity 配置信息
    public void configure(HttpSecurity http) throws Exception {http.csrf()
                .disable()
                .authorizeRequests()
                //.antMatchers("/user/query").hasAnyRole("USER")
                .anyRequest()
                .authenticated()
                .and()
                .addFilter(new TokenVerifyFilter(super.authenticationManager(), prop))
                // 禁用掉 session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
}

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556

去掉“减少自定义认证过滤器”即可!

编写产品处理器

package com.dpb.controller;

import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @program: springboot-54-security-jwt-demo
 * @description:
 * @author: 波波烤鸭
 * @create: 2019-12-03 11:55
 */
@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/query")
    public String query(){return "success";}

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

测试

搞定~

作者:波波烤鸭
起源:https://blog.csdn.net/qq_3852…

近期热文举荐:
SpringCloud 微服务电商我的项目教程

正文完
 0