一、什么是单点登陆 单点登录
(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的父依赖即可
1 2 3 4 5 6 7 <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相关的依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <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 > <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
1 2 3 4 5 6 7 8 9 10 11 12 13 @Data public class Payload <T>{ private String id; private T userInfo; private Date expiration; } 123456789101112
JsonUtils
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 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;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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 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;public class JwtUtils { private static final String JWT_PAYLOAD_USER_KEY = "user" ; 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(); } 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(); } 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())); } 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; } 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 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;public class RsaUtils { private static final int DEFAULT_KEY_SIZE = 2048 ; public static PublicKey getPublicKey (String filename) throws Exception { byte [] bytes = readFile(filename); return getPublicKey(bytes); } public static PrivateKey getPrivateKey (String filename) throws Exception { byte [] bytes = readFile(filename); return getPrivateKey(bytes); } 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); } 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); } 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公钥和私钥
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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认证系统创建 接下来我们创建我们的认证服务。
导入相关的依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <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
创建配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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
提供公钥私钥的配置类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 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;@Data @ConfigurationProperties(prefix = "rsa.key") public class RsaKeyProperties { private String pubKeyFile; private String priKeyFile; private PublicKey publicKey; private PrivateKey privateKey; @PostConstruct public void createRsaKey () throws Exception { publicKey = RsaUtils.getPublicKey(pubKeyFile); privateKey = RsaUtils.getPrivateKey(priKeyFile); } } 1234567891011121314151617181920212223242526272829303132333435363738
创建启动类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @SpringBootApplication @MapperScan("com.dpb.mapper") @EnableConfigurationProperties(RsaKeyProperties.class) public class App { public static void main (String[] args) { SpringApplication.run(App.class,args); } } 123456789101112131415
完成数据认证的逻辑 pojo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 package com.dpb.domain;import com.fasterxml.jackson.annotation.JsonIgnore;import lombok.Data;import org.springframework.security.core.GrantedAuthority;@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;@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接口
1 2 3 4 public interface UserMapper { public UserPojo queryByUserName (@Param("userName") String userName) ; } 123
Mapper映射文件
1 2 3 4 5 6 7 8 9 10 <?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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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
自定义认证过滤器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 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;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的过滤器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 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;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 " )) { 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 { String token = header.replace("Bearer " , "" ); 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的配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 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;@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()); } 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包根据实际业务导包即可,咱们就暂时和认证服务一样了。
接下来我们再创建一个资源服务
导入相关的依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <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
编写产品服务配置文件 切记这里只能有公钥地址!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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
编写读取公钥的配置类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 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;@Data @ConfigurationProperties(prefix = "rsa.key") public class RsaKeyProperties { private String pubKeyFile; private PublicKey publicKey; @PostConstruct public void createRsaKey () throws Exception { publicKey = RsaUtils.getPublicKey(pubKeyFile); } } 12345678910111213141516171819202122232425262728293031323334
编写启动类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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;@SpringBootApplication @MapperScan("com.dpb.mapper") @EnableConfigurationProperties(RsaKeyProperties.class) public class App { public static void main (String[] args) { SpringApplication.run(App.class,args); } } 1234567891011121314151617181920212223
复制认证服务中,用户对象,角色对象和校验认证的接口 复制认证服务中的相关内容即可
复制认证服务中SpringSecurity配置类做修改 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 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;@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()); } public void configure (HttpSecurity http) throws Exception { http.csrf() .disable() .authorizeRequests() .anyRequest() .authenticated() .and() .addFilter(new TokenVerifyFilter (super .authenticationManager(), prop)) .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); } } 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
去掉“增加自定义认证过滤器”即可!
编写产品处理器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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;@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…