两大话题

用户认证

授权

有状态&无状态

在单体架构的时代,应用常常通过session保持会话,通过将session保存到中央存储中去。常常会使用redis或memcached。在那个年代,要想搭建应用的集群,常常要借用tomcat的session共享插件或者借助sping session这样的小项目把应用的session存储到session store,以及从session store中查询session,这种方式我们认为是有状态的,因为服务端要记录用户的session.随着应用的发展,特别是近几年,微服务流行,越来越提到无状态,也就是服务端不再维护session。一个系统包括了很多的微服务,一个微服务也可能是集群。使用一个sesson store与微服务分而治之的思想是背道而驰的,如果session store挂了,那么不管有多少个微服务,系统全完蛋了,另外,如果我们session store要做迁移的话也很麻烦,因为所有的微服务都连接了这个session store,那么所有微服务都要去修改这个配置。也就是牵一发而动全身。第三,如果sessin store达到了容量瓶颈,那我们就需要扩容了。

那么无状态是怎么玩的呢?

在无状态的世界,服务器端一般不会存储用户的登录状态,而是在用户登录上颁发一个token,这个token一般是加密的,以后用户的每个请求都会带上这个token,可以存放在头信息中或者url参数中,服务端拿到token,解密一下,验证是否合法,是否过期,如果验证没问题,则认为用户没有问题。

无论是有状态还是无状态,session或是token本质上作用都是一样的,都是判断用户的一个凭证,只是无状态下服务器端只是对token做一个校验,而不需要进行存储。服务端不需要存储token,提高了微服务的伸缩。但是对于设置用户的登录有效期就比较难了。通过session可以很方便实现强制下线、登录有效期这个问题。

因此对于有状态,服务器端控制力强,但是存在中心点、鸡蛋在一个篮子里;迁移麻烦;服务器端存储数据,加大了服务器端压力。

而对于无状态去中心化,无存储、简单,任意扩容、缩容。但是缺点就是服务器端控制力相对弱。

目前无状态是越来越流行,但是也要结合实际的项目选择方案。

登录认证方案与选择
流向的微服务登录认证方案

认证方案1 - “处处安全”

https://www.cnblogs.com/cjsblog/p/10548022.html

OAuth2.0系列文章:

http://ifeve.com/oauth2-tutorial-all/

代表实现:

Spring Cloud Security:https://cloud.spring.io/spring-cloud-security/reference/html/

Jboss Keycloak : https://www.keycloak.org/

Spring Cloud Security认证授权实例代码:

https://github.com/chengjiansheng/cjs-oauth2-sso-demo

Keycloak认证授权实例代码:(基于servlet开发)

https://github.com/eacdy/spring-cloud-yes

优点:安全性高

缺点:实现成本高

这种方案此处不展开了

认证方案2 - 外部无状态,内部有状态

在这个方案下,网关不存放session,而是使用token,而网关代理点微服务使用session store共享session,看起来这种方案是很奇葩的,但是在实际中很多企业采用这种方案。这很奇怪,这种方案既没有利用到session的优势,也没有利用到无状态的优势,然后还比单纯的有状态或无状态方案还复杂。

如果你有一个很庞大的系统,早期采用了传统的架构,已经使用了session,但是现在微服务架构又采用token.

用户携带token和JSESSIONID请求API网关,网关做转发,老应用拿JSESSIONID到Session Store中查,如果能够查到,则用户已经登录了。对于微服务那一块,则解密token,验证用户是否登录,这样,就可以逐步进行重构,每次只重构遗留系统的一部分。

这种方案好像没啥优点,缺点一大点,但是可以逐步改造老项目,这是一个亮点。

认证方案3 - “ 内部裸奔”

这种方案是这样的,请求在网关上做登录认证,如果登录认证,登录成功网关就是颁发token,之后用户的每次请求都会携带这个token,网关会解密这个token,另外可以在token中存放用户的信息,网关可以解析token,获取这个信息,网关就知道是谁登录了。之后,网关会把解析出来的用户信息写到请求的http header信息中。在这种方案下,登录认证、token解密、token解析都是由网关去做的,并且网关告诉后端微服务用户是谁,微服务选择无条件去相信。优点是性能非常好,实现简单,只需要网关上添加过滤器工厂实现登录以及token解密,从而判断当前用户是否登录,以及token解析,获取到token中的信息。缺点是,网关一旦被攻破,那么用户的授权登录就形同虚设

认证方案4 - 内部裸奔 改进方案

请求经过网关,转发用户中心登录,如果登录成功,由用户中心颁发Token,以后用户的请求都会携带这个Token,网关不会操作这个token,而是发给后方的微服务,由微服务进行解密和解析Token,微服务之间调用也是传递Token,然后下一个微服务进行解密和解析。这种方案网关实现简单,不需要关注用户是谁这种强业务,另外微服务自己进行认证授权可以降低团队的沟通成本。这个方案提高了系统的安全性。但是安全性是相对的,每个微服务都需要做解密解析,知道密钥的就更多,导致密钥泄露的概率也就越大。所以使用这种方案要防止密钥泄露,可以定期更换密钥,不要开发者看到密钥本身等。优点是实现不负责,网关的工作简单,降低了团队的沟通成本,缺点是密钥一旦泄露,那就玩完了,但是这也是可以解决的。

以上的方案可以做出各种变种。

如何选择
方案 复杂度 安全性 性能 测试难度
处处安全 性能中等 难(一般做集成测试)
外部无状态 内部有状态 难(一般做集成测试)
内部裸奔 一般 简单(造Header即可实现接口测试)
内部裸奔改进版 中(造Token即可实现接口测试)
访问控制

所谓访问控制就是满足什么样的条件可以访问,我们也可以称之为授权。目前业界比较流行的访问控制方案有:

Access Control List(ACL)

Role-based access control (RBAC) – 最流行的模型 下面也以这种进行开展

Attribute-based access control(ABAC)

Rule-based access control

Time-based access control

JWT

这是一种比较流行的token,全称Json Web Token,是一个开放标准(RFC 7519),用来在各方之间安全地传输信息.JWT可被验证和信任,因为它是数字签名的。

JWT组成

组成 作用 内容示例
Header(头) 记录令牌类型、签名的算法等 {“alg”:“HS256”,“typ”:“JWT”}
Payload(有效载荷) 携带一些用户信息 {“userId”:“1”,“username”:“damu”}
Signature(签名) 防止token被篡改、确保安全性 计算出来的签名,一个字符串

用户微服务添加如下依赖:

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

添加工具类

package com.cloud.msuser.jwt;import java.util.Date;
import java.util.HashMap;
import java.util.Map;import javax.crypto.SecretKey;import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.security.Keys;@Component
public class JwtOperator {public static final Logger log = LoggerFactory.getLogger(JwtOperator.class);/*** 秘钥 - 默认aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrsssttt*/@Value("${secret:aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrsssttt}")private String secret;/*** 有效期,单位秒 - 默认2周*/@Value("${expire-time-in-second:1209600}")private Long expirationTimeInSecond;/*** 从token中获取claim** @param token token* @return claim*/public Claims getClaimsFromToken(String token) {try {return Jwts.parser().setSigningKey(this.secret.getBytes()).parseClaimsJws(token).getBody();} catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | IllegalArgumentException e) {log.error("token解析错误", e);throw new IllegalArgumentException("Token invalided.");}}/*** 获取token的过期时间** @param token token* @return 过期时间*/public Date getExpirationDateFromToken(String token) {return getClaimsFromToken(token).getExpiration();}/*** 判断token是否过期** @param token token* @return 已过期返回true,未过期返回false*/private Boolean isTokenExpired(String token) {Date expiration = getExpirationDateFromToken(token);return expiration.before(new Date());}/*** 计算token的过期时间** @return 过期时间*/private Date getExpirationTime() {return new Date(System.currentTimeMillis() + this.expirationTimeInSecond * 1000);}/*** 为指定用户生成token** @param claims 用户信息* @return token*/public String generateToken(Map<String, Object> claims) {Date createdTime = new Date();Date expirationTime = this.getExpirationTime();byte[] keyBytes = secret.getBytes();SecretKey key = Keys.hmacShaKeyFor(keyBytes);return Jwts.builder().setClaims(claims).setIssuedAt(createdTime).setExpiration(expirationTime)// 你也可以改用你喜欢的算法// 支持的算法详见:https://github.com/jwtk/jjwt#features.signWith(key, SignatureAlgorithm.HS256).compact();}/*** 判断token是否非法** @param token token* @return 未过期返回true,否则返回false*/public Boolean validateToken(String token) {return !isTokenExpired(token);}public static void main(String[] args) {// 1. 初始化JwtOperator jwtOperator = new JwtOperator();jwtOperator.expirationTimeInSecond = 1209600L;jwtOperator.secret = "aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrsssttt";// 2.设置用户信息HashMap<String, Object> objectObjectHashMap = new HashMap<>();objectObjectHashMap.put("id", "1");// 测试1: 生成tokenString token = jwtOperator.generateToken(objectObjectHashMap);// 会生成类似该字符串的内容:// eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEiLCJpYXQiOjE1ODI5ODA5MjIsImV4cCI6MTU4NDE5MDUyMn0.ldBmVbqqWNbySHk-nK4ew1Laf20LB_ok6P739keAApESystem.out.println(token);// 将我改成上面生成的token!!!String someToken = "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEiLCJpYXQiOjE1ODI5ODA5MjIsImV4cCI6MTU4NDE5MDUyMn0.ldBmVbqqWNbySHk-nK4ew1Laf20LB_ok6P739keAApE";// 测试2: 如果能token合法且未过期,返回trueBoolean validateToken = jwtOperator.validateToken(someToken);System.out.println(validateToken);   // true// 测试3: 获取用户信息Claims claims = jwtOperator.getClaimsFromToken(someToken);System.out.println(claims);  // {id=1, iat=1582980922, exp=1584190522}// 将我改成你生成的token的第一段(以.为边界)String encodedHeader = "eyJhbGciOiJIUzI1NiJ9";// 测试4: 解密Headerbyte[] header = Base64.decodeBase64(encodedHeader.getBytes());System.out.println(new String(header));  // {"alg":"HS256"}// 将我改成你生成的token的第二段(以.为边界)String encodedPayload = "eyJpZCI6IjEiLCJpYXQiOjE1ODI5ODA5MjIsImV4cCI6MTU4NDE5MDUyMn0";// 测试5: 解密Payloadbyte[] payload = Base64.decodeBase64(encodedPayload.getBytes());System.out.println(new String(payload));   // {"id":"1","iat":1582980922,"exp":1584190522}// 测试6: 这是一个被篡改的token,因此会报异常,说明JWT是安全的jwtOperator.validateToken("aeyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEiLCJpYXQiOjE1ODI5ODA5MjIsImV4cCI6MTU4NDE5MDUyMn0.ldBmVbqqWNbySHk-nK4ew1Laf20LB_ok6P739keAApE");}
}

添加配置

jwt:# 密钥secret: aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrsssttt# 有效期,单位秒,默认2周expire-time-in-second: 1209600

针对课程微服务 执行同样的操作 添加依赖 添加工具类 添加配置 注意 课程微服务与用户微服务使用的密钥必须是一样的 否则一个微服务颁发的token另一个微服务就没法使用了。

实现登录认证

创建dto对象

package com.cloud.msuser.domain.dto;public class UserLoginDTO {private String username;private String password;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}
}

添加jpa方法findByUsernameAndPassword

package com.cloud.msuser.repository;import java.util.Optional;import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;import com.cloud.msuser.domain.entity.User;@Repository
public interface UserRepository extends CrudRepository<User, Integer> {Optional<User> findByUsernameAndPassword(String username, String password);}

在用户微服务的UserController中添加方法login:

package com.cloud.msuser.controller;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;import com.cloud.msuser.domain.dto.UserLoginDTO;
import com.cloud.msuser.domain.entity.User;
import com.cloud.msuser.service.UserService;@RestController
public class UserController {@Autowiredprivate UserService userService;/*** http://localhost:8081/users/1* @param id* @return*/@GetMapping("/users/{id}")public User findById(@PathVariable Integer id) {return this.userService.findById(id);}@PostMapping("/login")public String login(@RequestBody UserLoginDTO loginDTO) {return this.userService.login(loginDTO);}
}

在UserService中添加方法

 @Autowiredprivate JwtOperator jwtOperator;public String login(UserLoginDTO loginDTO) {// 1. 效验账号密码[直接使用明文 实际项目要使用加密 目前比较流行的加密算法有MD5/BCript/SHA1]是否匹配// 让spring data jpareturn this.userRepository.findByUsernameAndPassword(loginDTO.getUsername(), loginDTO.getPassword()).map(user -> {// 2. 如果匹配,则颁发token,HashMap<String, Object> userInfo = new HashMap<String, Object>();userInfo.put("userId", user.getId());userInfo.put("username", user.getUsername());return jwtOperator.generateToken(userInfo);}).orElseThrow(() -> new IllegalArgumentException("账户密码不匹配"));}

启动用户微服务

2020-02-29 21:29:02.886  INFO 10936 --- [  restartedMain] o.s.b.devtools.restart.ChangeableUrls    : The Class-Path manifest attribute in D:\maven\repo\org\glassfish\jaxb\jaxb-runtime\2.3.2\jaxb-runtime-2.3.2.jar referenced one or more files that do not exist: file:/D:/maven/repo/org/glassfish/jaxb/jaxb-runtime/2.3.2/jakarta.xml.bind-api-2.3.2.jar,file:/D:/maven/repo/org/glassfish/jaxb/jaxb-runtime/2.3.2/txw2-2.3.2.jar,file:/D:/maven/repo/org/glassfish/jaxb/jaxb-runtime/2.3.2/istack-commons-runtime-3.0.8.jar,file:/D:/maven/repo/org/glassfish/jaxb/jaxb-runtime/2.3.2/stax-ex-1.8.1.jar,file:/D:/maven/repo/org/glassfish/jaxb/jaxb-runtime/2.3.2/FastInfoset-1.2.16.jar,file:/D:/maven/repo/org/glassfish/jaxb/jaxb-runtime/2.3.2/jakarta.activation-api-1.2.1.jar
2020-02-29 21:29:02.889  INFO 10936 --- [  restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2020-02-29 21:29:03.416  INFO 10936 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.retry.annotation.RetryConfiguration' of type [org.springframework.retry.annotation.RetryConfiguration$$EnhancerBySpringCGLIB$$8f150ae3] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying).   ____          _            __ _ _/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/  ___)| |_)| | | | | || (_| |  ) ) ) )'  |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot ::        (v2.2.2.RELEASE)2020-02-29 21:29:05.292  INFO 10936 --- [  restartedMain] com.cloud.msuser.MsUserApplication       : The following profiles are active: dev
2020-02-29 21:29:06.775  INFO 10936 --- [  restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2020-02-29 21:29:06.861  INFO 10936 --- [  restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 76ms. Found 2 JPA repository interfaces.
2020-02-29 21:29:07.058  WARN 10936 --- [  restartedMain] o.s.boot.actuate.endpoint.EndpointId     : Endpoint ID 'service-registry' contains invalid characters, please migrate to a valid format.
2020-02-29 21:29:07.446  INFO 10936 --- [  restartedMain] o.s.cloud.context.scope.GenericScope     : BeanFactory id=66fe5b88-5b85-38ce-88d7-3ba7e1992f8b
2020-02-29 21:29:07.581  INFO 10936 --- [  restartedMain] faultConfiguringBeanFactoryPostProcessor : No bean named 'errorChannel' has been explicitly defined. Therefore, a default PublishSubscribeChannel will be created.
2020-02-29 21:29:07.592  INFO 10936 --- [  restartedMain] faultConfiguringBeanFactoryPostProcessor : No bean named 'taskScheduler' has been explicitly defined. Therefore, a default ThreadPoolTaskScheduler will be created.
2020-02-29 21:29:07.599  INFO 10936 --- [  restartedMain] faultConfiguringBeanFactoryPostProcessor : No bean named 'integrationHeaderChannelRegistry' has been explicitly defined. Therefore, a default DefaultHeaderChannelRegistry will be created.
2020-02-29 21:29:07.671  INFO 10936 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.retry.annotation.RetryConfiguration' of type [org.springframework.retry.annotation.RetryConfiguration$$EnhancerBySpringCGLIB$$8f150ae3] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-02-29 21:29:07.704  INFO 10936 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-02-29 21:29:07.755  INFO 10936 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'integrationChannelResolver' of type [org.springframework.integration.support.channel.BeanFactoryChannelResolver] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-02-29 21:29:07.760  INFO 10936 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'integrationDisposableAutoCreatedBeans' of type [org.springframework.integration.config.annotation.Disposables] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-02-29 21:29:07.791  INFO 10936 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.integration.config.IntegrationManagementConfiguration' of type [org.springframework.integration.config.IntegrationManagementConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-02-29 21:29:07.798  INFO 10936 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration$IntegrationJmxConfiguration' of type [org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration$IntegrationJmxConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-02-29 21:29:07.811  INFO 10936 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration' of type [org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-02-29 21:29:07.818  INFO 10936 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'mbeanServer' of type [com.sun.jmx.mbeanserver.JmxMBeanServer] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-02-29 21:29:08.503  INFO 10936 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8081 (http)
2020-02-29 21:29:08.516  INFO 10936 --- [  restartedMain] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-02-29 21:29:08.516  INFO 10936 --- [  restartedMain] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.29]
2020-02-29 21:29:08.679  INFO 10936 --- [  restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2020-02-29 21:29:08.679  INFO 10936 --- [  restartedMain] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 3352 ms
2020-02-29 21:29:08.826  WARN 10936 --- [  restartedMain] c.n.c.sources.URLConfigurationSource     : No URLs will be polled as dynamic configuration sources.
2020-02-29 21:29:08.827  INFO 10936 --- [  restartedMain] c.n.c.sources.URLConfigurationSource     : To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.
2020-02-29 21:29:08.876  INFO 10936 --- [  restartedMain] c.netflix.config.DynamicPropertyFactory  : DynamicPropertyFactory is initialized with configuration sources: com.netflix.config.ConcurrentCompositeConfiguration@3819297b
2020-02-29 21:29:10.214  INFO 10936 --- [  restartedMain] o.s.s.c.ThreadPoolTaskScheduler          : Initializing ExecutorService 'taskScheduler'
2020-02-29 21:29:10.616  INFO 10936 --- [  restartedMain] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2020-02-29 21:29:10.704  INFO 10936 --- [  restartedMain] org.hibernate.Version                    : HHH000412: Hibernate Core {5.4.9.Final}
2020-02-29 21:29:10.885  INFO 10936 --- [  restartedMain] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.0.Final}
2020-02-29 21:29:11.022  INFO 10936 --- [  restartedMain] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2020-02-29 21:29:11.183  INFO 10936 --- [  restartedMain] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2020-02-29 21:29:11.206  INFO 10936 --- [  restartedMain] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.MySQL57Dialect
2020-02-29 21:29:11.990  INFO 10936 --- [  restartedMain] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2020-02-29 21:29:11.999  INFO 10936 --- [  restartedMain] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2020-02-29 21:29:12.913  WARN 10936 --- [  restartedMain] c.n.c.sources.URLConfigurationSource     : No URLs will be polled as dynamic configuration sources.
2020-02-29 21:29:12.913  INFO 10936 --- [  restartedMain] c.n.c.sources.URLConfigurationSource     : To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.
2020-02-29 21:29:13.010  WARN 10936 --- [  restartedMain] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2020-02-29 21:29:13.145  INFO 10936 --- [  restartedMain] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-02-29 21:29:13.867  INFO 10936 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2020-02-29 21:29:14.907  WARN 10936 --- [  restartedMain] ockingLoadBalancerClientRibbonWarnLogger : You already have RibbonLoadBalancerClient on your classpath. It will be used by default. As Spring Cloud Ribbon is in maintenance mode. We recommend switching to BlockingLoadBalancerClient instead. In order to use it, set the value of `spring.cloud.loadbalancer.ribbon.enabled` to `false` or remove spring-cloud-starter-netflix-ribbon from your project.
2020-02-29 21:29:14.967  INFO 10936 --- [  restartedMain] o.s.s.c.ThreadPoolTaskScheduler          : Initializing ExecutorService 'configWatchTaskScheduler'
2020-02-29 21:29:14.973  INFO 10936 --- [  restartedMain] o.s.s.c.ThreadPoolTaskScheduler          : Initializing ExecutorService 'catalogWatchTaskScheduler'
2020-02-29 21:29:15.002  INFO 10936 --- [  restartedMain] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 21 endpoint(s) beneath base path '/actuator'
2020-02-29 21:29:15.159  INFO 10936 --- [  restartedMain] o.s.c.s.m.DirectWithAttributesChannel    : Channel 'ms-user-1.input' has 1 subscriber(s).
2020-02-29 21:29:15.324  INFO 10936 --- [  restartedMain] o.s.i.monitor.IntegrationMBeanExporter   : Registering MessageChannel input
2020-02-29 21:29:15.457  INFO 10936 --- [  restartedMain] o.s.i.monitor.IntegrationMBeanExporter   : Registering MessageChannel nullChannel
2020-02-29 21:29:15.485  INFO 10936 --- [  restartedMain] o.s.i.monitor.IntegrationMBeanExporter   : Registering MessageChannel errorChannel
2020-02-29 21:29:15.581  INFO 10936 --- [  restartedMain] o.s.i.monitor.IntegrationMBeanExporter   : Registering MessageHandler org.springframework.cloud.stream.binding.StreamListenerMessageHandler@4e050db5
2020-02-29 21:29:15.691  INFO 10936 --- [  restartedMain] o.s.i.monitor.IntegrationMBeanExporter   : Registering MessageHandler errorLogger
2020-02-29 21:29:15.773  INFO 10936 --- [  restartedMain] o.s.i.endpoint.EventDrivenConsumer       : Adding {logging-channel-adapter:_org.springframework.integration.errorLogger} as a subscriber to the 'errorChannel' channel
2020-02-29 21:29:15.774  INFO 10936 --- [  restartedMain] o.s.i.channel.PublishSubscribeChannel    : Channel 'ms-user-1.errorChannel' has 1 subscriber(s).
2020-02-29 21:29:15.774  INFO 10936 --- [  restartedMain] o.s.i.endpoint.EventDrivenConsumer       : started bean '_org.springframework.integration.errorLogger'
2020-02-29 21:29:16.662  INFO 10936 --- [  restartedMain] c.s.b.r.p.RabbitExchangeQueueProvisioner : declaring queue for inbound: lesson-buy.g1, bound to: lesson-buy
2020-02-29 21:29:16.668  INFO 10936 --- [  restartedMain] o.s.a.r.c.CachingConnectionFactory       : Attempting to connect to: [192.168.99.100:5672]
2020-02-29 21:29:16.731  INFO 10936 --- [  restartedMain] o.s.a.r.c.CachingConnectionFactory       : Created new connection: rabbitConnectionFactory#28d113c9:0/SimpleConnection@6b129f9a [delegate=amqp://admin@192.168.99.100:5672/, localPort= 55315]
2020-02-29 21:29:16.809  INFO 10936 --- [  restartedMain] o.s.c.stream.binder.BinderErrorChannel   : Channel 'lesson-buy.g1.errors' has 1 subscriber(s).
2020-02-29 21:29:16.810  INFO 10936 --- [  restartedMain] o.s.c.stream.binder.BinderErrorChannel   : Channel 'lesson-buy.g1.errors' has 2 subscriber(s).
2020-02-29 21:29:16.837  INFO 10936 --- [  restartedMain] o.s.i.a.i.AmqpInboundChannelAdapter      : started bean 'inbound.lesson-buy.g1'
2020-02-29 21:29:16.870  INFO 10936 --- [  restartedMain] o.s.i.monitor.IntegrationMBeanExporter   : Registering MessageChannel lesson-buy.g1.errors
2020-02-29 21:29:17.019  INFO 10936 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8081 (http) with context path ''
2020-02-29 21:29:17.039  INFO 10936 --- [  restartedMain] o.s.c.c.s.ConsulServiceRegistry          : Registering service with consul: NewService{id='ms-user-8081', name='ms-user', tags=[a=b, c=d, JIFANG=NJ, secure=false], address='192.168.0.105', meta=null, port=8081, enableTagOverride=null, check=Check{script='null', interval='10s', ttl='null', http='http://192.168.0.105:8081/actuator/health', method='null', header={}, tcp='null', timeout='null', deregisterCriticalServiceAfter='null', tlsSkipVerify=null, status='null'}, checks=null}
2020-02-29 21:29:17.933  INFO 10936 --- [  restartedMain] com.cloud.msuser.MsUserApplication       : Started MsUserApplication in 16.741 seconds (JVM running for 18.002)
2020-02-29 21:29:20.673  INFO 10936 --- [nio-8081-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-02-29 21:29:20.674  INFO 10936 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2020-02-29 21:29:20.686  INFO 10936 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 12 ms

当前数据库数据有:

id username password money role reg_time
1 itmuch 1111 3 user 2/15/2020 14:37:20
2 jack 1234 5 user 2/29/2020 21:27:59


更换密码

登录状态效验

在userController中通过findById查询用户,必须首先登录过才可以。

 * 1. 如果用户没有登录,那么返回http 401* 2. 如果已经登录了,那么正常访问

要实现上面的业务,有多种方式:

  1. Servlet过滤器:实现javax.servlet.Filter接口
  2. 基于Spring MVC拦截器:实现org.springframework.web.servlet.HandlerInterceptor接口
  3. Spring AOP 通过注解实现

本次采用第三种方案,因为基于注解比较干净,另外aop是一个比较重要的知识点。

添加依赖:

      <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>

创建一个注解类

package com.cloud.msuser.auth;public @interface Login {}

创建一个异常类

package com.cloud.msuser.auth;public class SecurityException extends RuntimeException {private static final long serialVersionUID = 1080896099168520967L;public SecurityException() {super();}public SecurityException(String message) {super(message);}
}

创建一个切面

package com.cloud.msuser.auth;import javax.servlet.http.HttpServletRequest;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import com.cloud.msuser.jwt.JwtOperator;import io.jsonwebtoken.Claims;@Component
@Aspect
public class AuthAspect {@Autowiredprivate JwtOperator jwtOperator;@Around("@annotation(com.cloud.msuser.auth.Login)")public Object checkLogin(ProceedingJoinPoint point) throws Throwable {// 1.获取http请求中的header(token)ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();// 此处约定一下 头信息中包含AuthorizationString token = (String) request.getHeader("Authorization");if(StringUtils.isEmpty(token)) {throw new SecurityException("Token没有传!!");}// 2.校验token是否合法,如果合法就认为用户已登录,如果不合法,就返回401Boolean isValid = this.jwtOperator.validateToken(token);if (!isValid) {throw new SecurityException("Token非法!!");}Claims userInfo = this.jwtOperator.getClaimsFromToken(token);request.setAttribute("userId", userInfo.get("userId"));request.setAttribute("username", userInfo.get("username"));return point.proceed();}
}

在UserController的findById方法上添加注解

/*** 1. 如果用户没有登录,那么返回http 401* 2. 如果已经登录了,那么正常访问* http://localhost:8081/users/1* @param id* @return*/
@Login
@GetMapping("/users/{id}")
public User findById(@PathVariable Integer id) {return this.userService.findById(id);
}

再次启动用户微服务

2020-02-29 22:56:17.400  INFO 13000 --- [  restartedMain] o.s.b.devtools.restart.ChangeableUrls    : The Class-Path manifest attribute in D:\maven\repo\org\glassfish\jaxb\jaxb-runtime\2.3.2\jaxb-runtime-2.3.2.jar referenced one or more files that do not exist: file:/D:/maven/repo/org/glassfish/jaxb/jaxb-runtime/2.3.2/jakarta.xml.bind-api-2.3.2.jar,file:/D:/maven/repo/org/glassfish/jaxb/jaxb-runtime/2.3.2/txw2-2.3.2.jar,file:/D:/maven/repo/org/glassfish/jaxb/jaxb-runtime/2.3.2/istack-commons-runtime-3.0.8.jar,file:/D:/maven/repo/org/glassfish/jaxb/jaxb-runtime/2.3.2/stax-ex-1.8.1.jar,file:/D:/maven/repo/org/glassfish/jaxb/jaxb-runtime/2.3.2/FastInfoset-1.2.16.jar,file:/D:/maven/repo/org/glassfish/jaxb/jaxb-runtime/2.3.2/jakarta.activation-api-1.2.1.jar
2020-02-29 22:56:17.403  INFO 13000 --- [  restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2020-02-29 22:56:17.949  INFO 13000 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.retry.annotation.RetryConfiguration' of type [org.springframework.retry.annotation.RetryConfiguration$$EnhancerBySpringCGLIB$$e5d01e41] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying).   ____          _            __ _ _/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/  ___)| |_)| | | | | || (_| |  ) ) ) )'  |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot ::        (v2.2.2.RELEASE)2020-02-29 22:56:19.609  INFO 13000 --- [  restartedMain] com.cloud.msuser.MsUserApplication       : The following profiles are active: dev
2020-02-29 22:56:21.091  INFO 13000 --- [  restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2020-02-29 22:56:21.179  INFO 13000 --- [  restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 78ms. Found 2 JPA repository interfaces.
2020-02-29 22:56:21.379  WARN 13000 --- [  restartedMain] o.s.boot.actuate.endpoint.EndpointId     : Endpoint ID 'service-registry' contains invalid characters, please migrate to a valid format.
2020-02-29 22:56:21.722  INFO 13000 --- [  restartedMain] o.s.cloud.context.scope.GenericScope     : BeanFactory id=b2f66900-62c0-3838-b184-5633f95778ea
2020-02-29 22:56:21.833  INFO 13000 --- [  restartedMain] faultConfiguringBeanFactoryPostProcessor : No bean named 'errorChannel' has been explicitly defined. Therefore, a default PublishSubscribeChannel will be created.
2020-02-29 22:56:21.840  INFO 13000 --- [  restartedMain] faultConfiguringBeanFactoryPostProcessor : No bean named 'taskScheduler' has been explicitly defined. Therefore, a default ThreadPoolTaskScheduler will be created.
2020-02-29 22:56:21.846  INFO 13000 --- [  restartedMain] faultConfiguringBeanFactoryPostProcessor : No bean named 'integrationHeaderChannelRegistry' has been explicitly defined. Therefore, a default DefaultHeaderChannelRegistry will be created.
2020-02-29 22:56:21.905  INFO 13000 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.retry.annotation.RetryConfiguration' of type [org.springframework.retry.annotation.RetryConfiguration$$EnhancerBySpringCGLIB$$e5d01e41] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-02-29 22:56:21.954  INFO 13000 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-02-29 22:56:22.137  INFO 13000 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'integrationChannelResolver' of type [org.springframework.integration.support.channel.BeanFactoryChannelResolver] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-02-29 22:56:22.142  INFO 13000 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'integrationDisposableAutoCreatedBeans' of type [org.springframework.integration.config.annotation.Disposables] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-02-29 22:56:22.205  INFO 13000 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.integration.config.IntegrationManagementConfiguration' of type [org.springframework.integration.config.IntegrationManagementConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-02-29 22:56:22.235  INFO 13000 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration$IntegrationJmxConfiguration' of type [org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration$IntegrationJmxConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-02-29 22:56:22.250  INFO 13000 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration' of type [org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-02-29 22:56:22.259  INFO 13000 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'mbeanServer' of type [com.sun.jmx.mbeanserver.JmxMBeanServer] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-02-29 22:56:23.296  INFO 13000 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8081 (http)
2020-02-29 22:56:23.306  INFO 13000 --- [  restartedMain] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-02-29 22:56:23.306  INFO 13000 --- [  restartedMain] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.29]
2020-02-29 22:56:23.470  INFO 13000 --- [  restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2020-02-29 22:56:23.470  INFO 13000 --- [  restartedMain] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 3832 ms
2020-02-29 22:56:23.664  WARN 13000 --- [  restartedMain] c.n.c.sources.URLConfigurationSource     : No URLs will be polled as dynamic configuration sources.
2020-02-29 22:56:23.664  INFO 13000 --- [  restartedMain] c.n.c.sources.URLConfigurationSource     : To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.
2020-02-29 22:56:23.682  INFO 13000 --- [  restartedMain] c.netflix.config.DynamicPropertyFactory  : DynamicPropertyFactory is initialized with configuration sources: com.netflix.config.ConcurrentCompositeConfiguration@57c47da2
2020-02-29 22:56:25.582  INFO 13000 --- [  restartedMain] o.s.s.c.ThreadPoolTaskScheduler          : Initializing ExecutorService 'taskScheduler'
2020-02-29 22:56:26.370  INFO 13000 --- [  restartedMain] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2020-02-29 22:56:26.448  INFO 13000 --- [  restartedMain] org.hibernate.Version                    : HHH000412: Hibernate Core {5.4.9.Final}
2020-02-29 22:56:26.585  INFO 13000 --- [  restartedMain] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.0.Final}
2020-02-29 22:56:26.707  INFO 13000 --- [  restartedMain] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2020-02-29 22:56:26.850  INFO 13000 --- [  restartedMain] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2020-02-29 22:56:26.870  INFO 13000 --- [  restartedMain] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.MySQL57Dialect
2020-02-29 22:56:27.880  INFO 13000 --- [  restartedMain] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2020-02-29 22:56:27.888  INFO 13000 --- [  restartedMain] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2020-02-29 22:56:28.561  WARN 13000 --- [  restartedMain] c.n.c.sources.URLConfigurationSource     : No URLs will be polled as dynamic configuration sources.
2020-02-29 22:56:28.562  INFO 13000 --- [  restartedMain] c.n.c.sources.URLConfigurationSource     : To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.
2020-02-29 22:56:28.692  WARN 13000 --- [  restartedMain] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2020-02-29 22:56:28.896  INFO 13000 --- [  restartedMain] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-02-29 22:56:30.026  INFO 13000 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2020-02-29 22:56:31.113  WARN 13000 --- [  restartedMain] ockingLoadBalancerClientRibbonWarnLogger : You already have RibbonLoadBalancerClient on your classpath. It will be used by default. As Spring Cloud Ribbon is in maintenance mode. We recommend switching to BlockingLoadBalancerClient instead. In order to use it, set the value of `spring.cloud.loadbalancer.ribbon.enabled` to `false` or remove spring-cloud-starter-netflix-ribbon from your project.
2020-02-29 22:56:31.230  INFO 13000 --- [  restartedMain] o.s.s.c.ThreadPoolTaskScheduler          : Initializing ExecutorService 'configWatchTaskScheduler'
2020-02-29 22:56:31.241  INFO 13000 --- [  restartedMain] o.s.s.c.ThreadPoolTaskScheduler          : Initializing ExecutorService 'catalogWatchTaskScheduler'
2020-02-29 22:56:31.408  INFO 13000 --- [  restartedMain] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 21 endpoint(s) beneath base path '/actuator'
2020-02-29 22:56:31.661  INFO 13000 --- [  restartedMain] o.s.c.s.m.DirectWithAttributesChannel    : Channel 'ms-user-1.input' has 1 subscriber(s).
2020-02-29 22:56:31.824  INFO 13000 --- [  restartedMain] o.s.i.monitor.IntegrationMBeanExporter   : Registering MessageChannel errorChannel
2020-02-29 22:56:31.929  INFO 13000 --- [  restartedMain] o.s.i.monitor.IntegrationMBeanExporter   : Registering MessageChannel input
2020-02-29 22:56:31.979  INFO 13000 --- [  restartedMain] o.s.i.monitor.IntegrationMBeanExporter   : Registering MessageChannel nullChannel
2020-02-29 22:56:32.008  INFO 13000 --- [  restartedMain] o.s.i.monitor.IntegrationMBeanExporter   : Registering MessageHandler org.springframework.cloud.stream.binding.StreamListenerMessageHandler@72f958d1
2020-02-29 22:56:32.095  INFO 13000 --- [  restartedMain] o.s.i.monitor.IntegrationMBeanExporter   : Registering MessageHandler errorLogger
2020-02-29 22:56:32.171  INFO 13000 --- [  restartedMain] o.s.i.endpoint.EventDrivenConsumer       : Adding {logging-channel-adapter:_org.springframework.integration.errorLogger} as a subscriber to the 'errorChannel' channel
2020-02-29 22:56:32.171  INFO 13000 --- [  restartedMain] o.s.i.channel.PublishSubscribeChannel    : Channel 'ms-user-1.errorChannel' has 1 subscriber(s).
2020-02-29 22:56:32.172  INFO 13000 --- [  restartedMain] o.s.i.endpoint.EventDrivenConsumer       : started bean '_org.springframework.integration.errorLogger'
2020-02-29 22:56:32.892  INFO 13000 --- [  restartedMain] c.s.b.r.p.RabbitExchangeQueueProvisioner : declaring queue for inbound: lesson-buy.g1, bound to: lesson-buy
2020-02-29 22:56:32.895  INFO 13000 --- [  restartedMain] o.s.a.r.c.CachingConnectionFactory       : Attempting to connect to: [192.168.99.100:5672]
2020-02-29 22:56:32.942  INFO 13000 --- [  restartedMain] o.s.a.r.c.CachingConnectionFactory       : Created new connection: rabbitConnectionFactory#2292367c:0/SimpleConnection@3967bc8d [delegate=amqp://admin@192.168.99.100:5672/, localPort= 59128]
2020-02-29 22:56:33.008  INFO 13000 --- [  restartedMain] o.s.c.stream.binder.BinderErrorChannel   : Channel 'lesson-buy.g1.errors' has 1 subscriber(s).
2020-02-29 22:56:33.008  INFO 13000 --- [  restartedMain] o.s.c.stream.binder.BinderErrorChannel   : Channel 'lesson-buy.g1.errors' has 2 subscriber(s).
2020-02-29 22:56:33.034  INFO 13000 --- [  restartedMain] o.s.i.a.i.AmqpInboundChannelAdapter      : started bean 'inbound.lesson-buy.g1'
2020-02-29 22:56:33.061  INFO 13000 --- [  restartedMain] o.s.i.monitor.IntegrationMBeanExporter   : Registering MessageChannel lesson-buy.g1.errors
2020-02-29 22:56:33.202  INFO 13000 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8081 (http) with context path ''
2020-02-29 22:56:33.220  INFO 13000 --- [  restartedMain] o.s.c.c.s.ConsulServiceRegistry          : Registering service with consul: NewService{id='ms-user-8081', name='ms-user', tags=[a=b, c=d, JIFANG=NJ, secure=false], address='192.168.0.105', meta=null, port=8081, enableTagOverride=null, check=Check{script='null', interval='10s', ttl='null', http='http://192.168.0.105:8081/actuator/health', method='null', header={}, tcp='null', timeout='null', deregisterCriticalServiceAfter='null', tlsSkipVerify=null, status='null'}, checks=null}
2020-02-29 22:56:33.754  INFO 13000 --- [  restartedMain] com.cloud.msuser.MsUserApplication       : Started MsUserApplication in 17.589 seconds (JVM running for 18.892)
2020-02-29 22:56:39.852  INFO 13000 --- [nio-8081-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-02-29 22:56:39.853  INFO 13000 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2020-02-29 22:56:39.875  INFO 13000 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 22 ms



通过post请求获取一个token:

eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjIsInVzZXJuYW1lIjoiamFjayIsImlhdCI6MTU4Mjk4ODMyNywiZXhwIjoxNTg0MTk3OTI3fQ.UisHgud-IvPOKXDG8R15h84SeFMuCflbwTRrClpDOUM


但是上面请求失败返回的不是401,而是500,通过全局异常处理器进行处理:

package com.cloud.msuser.auth;import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(SecurityException.class)public ResponseEntity<Response> result(SecurityException exception) {Response response = new Response(exception.getMessage(), 401);return new ResponseEntity<Response>(response, HttpStatus.UNAUTHORIZED);}
}class Response {private String message;private Integer code;public Response() {}public Response(String message, Integer code) {this.message = message;this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}
}


但是还存在如下问题 如果传递错了token时 返回的是500 而且异常信息为 Token invalided.

这是因为切面里面在验证token有效性时抛出了异常,修改代码,对切面代码进行整体try…catch…处理

package com.cloud.msuser.auth;public class SecurityException extends RuntimeException {private static final long serialVersionUID = 1080896099168520967L;public SecurityException() {super();}public SecurityException(String message) {super(message);}public SecurityException(String message, Throwable cause) {super(message, cause);}public SecurityException(Throwable cause) {super(cause);}
}
package com.cloud.msuser.auth;import javax.servlet.http.HttpServletRequest;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import com.cloud.msuser.jwt.JwtOperator;import io.jsonwebtoken.Claims;@Component
@Aspect
public class AuthAspect {@Autowiredprivate JwtOperator jwtOperator;@Around("@annotation(com.cloud.msuser.auth.Login)")public Object checkLogin(ProceedingJoinPoint point) {try {// 1.获取http请求中的header(token)ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();// 此处约定一下 头信息中包含AuthorizationString token = (String) request.getHeader("Authorization");if (StringUtils.isEmpty(token)) {throw new SecurityException("Token没有传!!");}// 2.校验token是否合法,如果合法就认为用户已登录,如果不合法,就返回401Boolean isValid = this.jwtOperator.validateToken(token);if (!isValid) {throw new SecurityException("Token非法!!");}Claims userInfo = this.jwtOperator.getClaimsFromToken(token);request.setAttribute("userId", userInfo.get("userId"));request.setAttribute("username", userInfo.get("username"));return point.proceed();} catch (Throwable e) {throw new SecurityException(e);}}
}

课程微服务实现登录状态效验

跟用户微服务一样引入依赖包、注解以及切面

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
package com.cloud.msclass.auth;public @interface Login {}
package com.cloud.msclass.auth;public class SecurityException extends RuntimeException {private static final long serialVersionUID = 1080896099168520967L;public SecurityException() {super();}public SecurityException(String message) {super(message);}public SecurityException(String message, Throwable cause) {super(message, cause);}public SecurityException(Throwable cause) {super(cause);}
}
package com.cloud.msclass.auth;import javax.servlet.http.HttpServletRequest;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import com.cloud.msclass.jwt.JwtOperator;import io.jsonwebtoken.Claims;@Component
@Aspect
public class AuthAspect {@Autowiredprivate JwtOperator jwtOperator;@Around("@annotation(com.cloud.msuser.auth.Login)")public Object checkLogin(ProceedingJoinPoint point) {try {// 1.获取http请求中的header(token)ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();// 此处约定一下 头信息中包含AuthorizationString token = (String) request.getHeader("Authorization");if (StringUtils.isEmpty(token)) {throw new SecurityException("Token没有传!!");}// 2.校验token是否合法,如果合法就认为用户已登录,如果不合法,就返回401Boolean isValid = this.jwtOperator.validateToken(token);if (!isValid) {throw new SecurityException("Token非法!!");}Claims userInfo = this.jwtOperator.getClaimsFromToken(token);request.setAttribute("userId", userInfo.get("userId"));request.setAttribute("username", userInfo.get("username"));return point.proceed();} catch (Throwable e) {throw new SecurityException(e);}}
}
package com.cloud.msclass.auth;import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(SecurityException.class)public ResponseEntity<Response> result(SecurityException exception) {Response response = new Response(exception.getMessage(), 401);return new ResponseEntity<Response>(response, HttpStatus.UNAUTHORIZED);}
}class Response {private String message;private Integer code;public Response() {}public Response(String message, Integer code) {this.message = message;this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}
}

首先执行用户登录,然后再执行课程微服务的买课程服务,如下:、

首先删掉lesson_user表中的数据:

再次执行操作

http://localhost:8010/lesssons/buy/1

{"id": null,"title": null,"cover": null,"price": null,"description": null,"createTime": null,"videoUrl": null
}

查看后台日志:

2020-03-01 12:50:30.846 DEBUG 13556 --- [nio-8010-exec-3] c.cloud.msclass.feign.MsUserFeignClient  : [MsUserFeignClient#findUserById] ---> GET http://ms-user/users/2 HTTP/1.1
2020-03-01 12:50:30.846 DEBUG 13556 --- [nio-8010-exec-3] c.cloud.msclass.feign.MsUserFeignClient  : [MsUserFeignClient#findUserById] ---> END HTTP (0-byte body)
2020-03-01 12:50:31.020 DEBUG 13556 --- [nio-8010-exec-3] c.cloud.msclass.feign.MsUserFeignClient  : [MsUserFeignClient#findUserById] <--- HTTP/1.1 401  (173ms)
2020-03-01 12:50:31.021 DEBUG 13556 --- [nio-8010-exec-3] c.cloud.msclass.feign.MsUserFeignClient  : [MsUserFeignClient#findUserById] connection: keep-alive
2020-03-01 12:50:31.021 DEBUG 13556 --- [nio-8010-exec-3] c.cloud.msclass.feign.MsUserFeignClient  : [MsUserFeignClient#findUserById] content-type: application/json
2020-03-01 12:50:31.021 DEBUG 13556 --- [nio-8010-exec-3] c.cloud.msclass.feign.MsUserFeignClient  : [MsUserFeignClient#findUserById] date: Sun, 01 Mar 2020 04:50:31 GMT
2020-03-01 12:50:31.021 DEBUG 13556 --- [nio-8010-exec-3] c.cloud.msclass.feign.MsUserFeignClient  : [MsUserFeignClient#findUserById] keep-alive: timeout=60
2020-03-01 12:50:31.021 DEBUG 13556 --- [nio-8010-exec-3] c.cloud.msclass.feign.MsUserFeignClient  : [MsUserFeignClient#findUserById] transfer-encoding: chunked
2020-03-01 12:50:31.021 DEBUG 13556 --- [nio-8010-exec-3] c.cloud.msclass.feign.MsUserFeignClient  : [MsUserFeignClient#findUserById]
2020-03-01 12:50:31.022 DEBUG 13556 --- [nio-8010-exec-3] c.cloud.msclass.feign.MsUserFeignClient  : [MsUserFeignClient#findUserById] {"message":"com.cloud.msuser.auth.SecurityException: Token没有传!!","code":401}
2020-03-01 12:50:31.022 DEBUG 13556 --- [nio-8010-exec-3] c.cloud.msclass.feign.MsUserFeignClient  : [MsUserFeignClient#findUserById] <--- END HTTP (86-byte body)
2020-03-01 12:50:31.046 ERROR 13556 --- [nio-8010-exec-3] c.c.msclass.controller.LessonController  : 发生fallbackcom.cloud.msclass.auth.SecurityException: feign.FeignException$Unauthorized: status 401 reading MsUserFeignClient#findUserById(Integer)

由此可见 是由于feign请求导致出现了异常 然后触发了fallback,但是我们希望的是通过SecurityException,再触发401

因此需要修改切面与服务容错的先后顺序

// 添加Order注解
@Order(1)
@Component
@Aspect
public class AuthAspect {@Autowiredprivate JwtOperator jwtOperator;@Around("@annotation(com.cloud.msclass.auth.Login)")public Object checkLogin(ProceedingJoinPoint point) {try {// 1.获取http请求中的header(token)ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();// 此处约定一下 头信息中包含AuthorizationString token = (String) request.getHeader("Authorization");if (StringUtils.isEmpty(token)) {throw new SecurityException("Token没有传!!");}// 2.校验token是否合法,如果合法就认为用户已登录,如果不合法,就返回401Boolean isValid = this.jwtOperator.validateToken(token);if (!isValid) {throw new SecurityException("Token非法!!");}Claims userInfo = this.jwtOperator.getClaimsFromToken(token);request.setAttribute("userId", userInfo.get("userId"));request.setAttribute("username", userInfo.get("username"));return point.proceed();} catch (Throwable e) {throw new SecurityException(e);}}
}

查看日志

2020-03-01 12:50:31.002  WARN 12596 --- [nio-8081-exec-6] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [com.cloud.msuser.auth.SecurityException: com.cloud.msuser.auth.SecurityException: Token没有传!!]
2020-03-01 12:57:14.696  WARN 12596 --- [nio-8081-exec-9] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [com.cloud.msuser.auth.SecurityException: com.cloud.msuser.auth.SecurityException: Token没有传!!]
{"id": null,"title": null,"cover": null,"price": null,"description": null,"createTime": null,"videoUrl": null
}

如果此时token错误的话,则返回

2020-03-01 13:05:37.531  WARN 10616 --- [nio-8010-exec-9] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [com.cloud.msclass.auth.SecurityException: java.lang.IllegalArgumentException: Token invalided.]
{"message": "java.lang.IllegalArgumentException: Token invalided.","code": 401
}

下面需要解决调用feign接口调用的错误问题

Feign实现Token传递

修改feign接口以及相应的代码(添加token参数)

@FeignClient(name = "ms-user") // 底层使用ribbon去请求
public interface MsUserFeignClient {@GetMapping("/users/{userId}")UserDTO findUserById(@PathVariable("userId") Integer userId,@RequestHeader("Authorization") String token);
}
@Service
@Transactional(rollbackFor = Exception.class)
public class LessonService {@Autowiredprivate LessonRepository lessonRepository;@Autowiredprivate LessonUserRepository lessonUserRepository;@Autowiredprivate Source source;@Autowiredprivate MsUserFeignClient msUserFeignClient;public Lesson buyById(Integer id, Integer userId,String token) {// 1. 根据id查询lessonLesson lesson = this.lessonRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("该课程不存在"));// 2. 根据lesson.id查询user_lesson,那么直接返回lessonLessonUser lessonUser = this.lessonUserRepository.findByLessonId(id);if (lessonUser != null) {return lesson;}// 3. 如果user_lesson==null && 用户的余额 > lesson.price 则购买成功UserDTO userDTO = this.msUserFeignClient.findUserById(userId,token);BigDecimal money = userDTO.getMoney().subtract(lesson.getPrice());if (money.doubleValue() < 0) {throw new IllegalArgumentException("余额不足");}// 购买逻辑 ... 1. 发送消息给用户微服务 让它扣减金额String description = String.format("%s购买了id为%s的课程", userId, id);this.source.output().send(MessageBuilder.withPayload(new UserMoneyDTO(userId, lesson.getPrice(), "购买课程", description)).build());// 2.向lesson_user表插入数据LessonUser lu = new LessonUser();lu.setLessonId(id);lu.setUserId(userId);this.lessonUserRepository.save(lu);return lesson;}
}
@RestController
@RequestMapping("lesssons")
public class LessonController {private static final Logger logger = LoggerFactory.getLogger(LessonController.class);@Autowiredprivate LessonService lessonService;/*** http://localhost:8010/lesssons/buy/1 购买指定id的课程* * @param id*/@GetMapping("/buy/{id}")@RateLimiter(name = "buyById", fallbackMethod = "buyByIdFallBack")@Loginpublic Lesson buyById(@PathVariable Integer id, HttpServletRequest request,@RequestHeader("Authorization") String token) {Integer userId = (Integer) request.getAttribute("userId");return this.lessonService.buyById(id, userId, token);}// 必须与原方法有相同的返回值和参数(侯曼带一个Throwable参数)public Lesson buyByIdFallBack(@PathVariable Integer id, HttpServletRequest request, String token,Throwable throwable) {// 表示从本地缓存获取logger.error("发生fallback", throwable);return new Lesson();}
}

执行http://localhost:8010/lesssons/buy/1操作(header附带token信息):

{"id": 1,"title": "SpringCloud视频教程","cover": "xxx","price": 5,"description": "SpringCloud视频教程","createTime": "2020-02-15T15:50:35.000+0000","videoUrl": "https://ke.qq.com/classroom/index.html"
}


但是在实际项目中,如此修改,如果接口非常多,则非常麻烦,因此采用第二种方案:

RequestInterceptor

将上面所有加了token的代码进行还原 并删除数据库中lesson_user的数据

package com.cloud.msclass.interceptor;import javax.servlet.http.HttpServletRequest;import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import feign.RequestInterceptor;
import feign.RequestTemplate;public class MyHeaderRequestInterceptor implements RequestInterceptor {@Overridepublic void apply(RequestTemplate template) {ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();String token = (String) request.getHeader("Authorization");template.header("Authorization", token);}
}

添加feign配置

feign:client:config:default:logger-level: fullrequest-interceptors:- com.cloud.msclass.interceptor.MyHeaderRequestInterceptor

再次测试,OK

下面在controller方法上添加服务容错的注解(仓壁模式):

 @GetMapping("/buy/{id}")@RateLimiter(name = "buyById", fallbackMethod = "buyByIdFallBack")@Bulkhead(name = "buyById", fallbackMethod = "buyByIdFallBack",type = Type.THREADPOOL)@Loginpublic Lesson buyById(@PathVariable Integer id, HttpServletRequest request) {Integer userId = (Integer) request.getAttribute("userId");return this.lessonService.buyById(id, userId);}

程序会报错:

2020-03-01 13:40:09.992 ERROR 9992 --- [nio-8010-exec-3] c.c.msclass.controller.LessonController  : 发生fallbackjava.lang.IllegalStateException: ThreadPool bulkhead is only applicable for completable futures

因为基于线程池的Bulkhead无法传递ThreadLocal.因为MyHeaderRequestInterceptor中引用的RequestContextHolder是通过ThreadLocal来保存和传递线程的。如果应用使用了ThreadLocal,不要去使用基于线程池的BulkHead.

删掉上面增加的Bulkhead注解。

RestTemplate传递Token
通过exchange
 @Autowiredprivate RestTemplate restTemplate;@GetMapping("/test-token-relay")public ResponseEntity<UserDTO> testTokenRelay(@RequestHeader("Authorization") String token) {HttpHeaders headers = new HttpHeaders();headers.set("Authorization", token);return this.restTemplate.exchange("http://ms-user/users/{id}", HttpMethod.GET, new HttpEntity<String>(headers),UserDTO.class, 2);}

通过POSTMAN发起请求 http://localhost:8010/test-token-relay header中包含token信息 返回结果如下:

{"id": 2,"username": "jack","password": "1234","money": 145,"role": "user","regTime": "2020-02-29T21:27:59.000+0000"
}

通过ClientHttpRequestInteceptor接口
package com.cloud.msclass.interceptor;import java.io.IOException;import javax.servlet.http.HttpServletRequest;import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;public class TokenRelayRequestInteceptor implements ClientHttpRequestInterceptor {@Overridepublic ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)throws IOException {HttpHeaders headers = request.getHeaders();ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest servletRequest = attributes.getRequest();// 此处约定一下 头信息中包含AuthorizationString token = (String) servletRequest.getHeader("Authorization");headers.add("Authorization", token);// 保证请求继续执行...return execution.execute(request, body);}
}

修改RestTemplate类实例:

 /*** spring web提供的轻量级http client* * @return*/@Bean@LoadBalancedpublic RestTemplate restTemplate() {RestTemplate restTemplate = new RestTemplate();restTemplate.setInterceptors(Collections.singletonList(new TokenRelayRequestInteceptor()));return restTemplate;}

在TestController中添加测试方法:

/*** http://localhost:8010/test-token-relay2* * @param token* @return*/
@GetMapping("/test-token-relay2")
public UserDTO testTokenRelay2() {return this.restTemplate.getForObject("http://ms-user/users/{id}", UserDTO.class, 2);
}
授权

在login服务中添加role信息

public String login(UserLoginDTO loginDTO) {// 1. 效验账号密码[直接使用明文 实际项目要使用加密 目前比较流行的加密算法有MD5/BCript/SHA1]是否匹配// 让spring data jpareturn this.userRepository.findByUsernameAndPassword(loginDTO.getUsername(), loginDTO.getPassword()).map(user -> {// 2. 如果匹配,则颁发token,HashMap<String, Object> userInfo = new HashMap<String, Object>();userInfo.put("userId", user.getId());userInfo.put("username", user.getUsername());// 添加上role信息userInfo.put("role", user.getRole());return jwtOperator.generateToken(userInfo);}).orElseThrow(() -> new IllegalArgumentException("账户密码不匹配"));
}
package com.cloud.msclass.auth;import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;/** Retention 指定注解的保留策略* RUNTIME 注解会在字节码中存在,并且可以通过反射获取* 元注解: 注解在注解类上的注解*/
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckAuthz {String hasRole();
}

在切面中添加验证角色的逻辑

package com.cloud.msclass.auth;import java.lang.reflect.Method;
import java.util.Objects;import javax.servlet.http.HttpServletRequest;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import com.cloud.msclass.jwt.JwtOperator;import io.jsonwebtoken.Claims;@Order(1)
@Component
@Aspect
public class AuthAspect {@Autowiredprivate JwtOperator jwtOperator;@Around("@annotation(com.cloud.msclass.auth.Login)")public Object checkLogin(ProceedingJoinPoint point) {try {// 1.获取http请求中的header(token)validateToken();return point.proceed();} catch (Throwable e) {throw new SecurityException(e);}}@Around("@annotation(com.cloud.msclass.auth.CheckAuthz)")public Object checkAuth(ProceedingJoinPoint point) {try {// 1.获取http请求中的header(token)HttpServletRequest request = validateToken();// 2 判断角色是否OKObject role = request.getAttribute("role");MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();CheckAuthz checkAuthz = method.getAnnotation(CheckAuthz.class);String hasRole = checkAuthz.hasRole();if (!Objects.equals(hasRole, role)) {throw new SecurityException("当前用户不具有角色" + hasRole);}return point.proceed();} catch (Throwable e) {throw new SecurityException(e);}}private HttpServletRequest validateToken() {ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();// 此处约定一下 头信息中包含AuthorizationString token = (String) request.getHeader("Authorization");if (StringUtils.isEmpty(token)) {throw new SecurityException("Token没有传!!");}// 2.校验token是否合法,如果合法就认为用户已登录,如果不合法,就返回401Boolean isValid = this.jwtOperator.validateToken(token);if (!isValid) {throw new SecurityException("Token非法!!");}Claims userInfo = this.jwtOperator.getClaimsFromToken(token);request.setAttribute("userId", userInfo.get("userId"));request.setAttribute("username", userInfo.get("username"));request.setAttribute("role", userInfo.get("role"));return request;}
}

创建测试方法

/*** 当且仅当当前用户是vip 才可以访问 http://localhost:8010/vip* * @param token* @return*/
@GetMapping("/vip")
@CheckAuthz(hasRole = "vip")
public UserDTO vip() {return this.restTemplate.getForObject("http://ms-user/users/{id}", UserDTO.class, 2);
}

测试:

-- 保证数据库中存在如下数据
INSERT INTO `ms_user`.`user` (`id`, `username`, `password`, `money`, `role`, `reg_time`) VALUES ('2', 'jack', '1234', '145', 'user', '2020-02-29 21:27:59');
INSERT INTO `ms_user`.`user` (`id`, `username`, `password`, `money`, `role`, `reg_time`) VALUES ('3', 'king', '1234', '145', 'vip', '2020-02-29 21:27:59');

登录用户jack

并访问 http://localhost:8010/vip

返回如下

{"message": "com.cloud.msclass.auth.SecurityException: 当前用户不具有角色vip","code": 401
}

登录用户king

并访问 http://localhost:8010/vip

返回如下

{"id": 2,"username": "jack","password": "1234","money": 145,"role": "user","regTime": "2020-02-29T21:27:59.000+0000"
}

可以查询到数据库信息

总结

登录认证的四种方案

授权常用方案

基于AOP实现认证授权

Feign/RestTemplate如何传递Token

11微服务认证与授权相关推荐

  1. Spring Cloud OAuth2 JWT 微服务认证服务器得构建

    文章目录 Spring Cloud OAuth2 JWT 微服务认证服务器得构建 前言 认证服务得搭建 `AuthorizationServer` `WebSecurityConfig` `Autho ...

  2. 微服务认证架构如何演进来的?

    [答疑解惑]| 作者 / Edison Zhou 这是恰童鞋骚年的第267篇原创内容 之前有同事问为何要用基于JWT令牌的认证架构,然后近期又有童鞋在后台留言问微服务安全认证架构的实践,因此我决定花两 ...

  3. Kong社区版集成Keycloak实现微服务认证与鉴权

    文章目录 Kong社区版集成Keycloak实现微服务认证与鉴权 前言 认证和鉴权流程 在Keycloak上配置 创建Realm 创建Client 创建Role 创建User 服务 环境准备 受保护的 ...

  4. Spring Security OAuth2 微服务认证中心自定义授权模式扩展以及常见登录认证场景下的应用实战

    本文源码地址 后端:https://gitee.com/youlaitech/youlai-mall/tree/v2.0.1 前端:https://gitee.com/youlaiorg/mall-a ...

  5. 微服务集成cas_Spring Cloud Security集成CAS (单点登录)对微服务认证

    一.前言 由于leader要求在搭好的spring cloud 框架中加入对微服务的认证包括单点登录认证,来确保系统的安全,所以研究了Spring Cloud Security这个组件.在前面搭好的d ...

  6. 微服务集成cas_Spring Cloud(四) Spring Cloud Security集成CAS (单点登录)对微服务认证...

    一.前言 由于leader要求在搭好的spring cloud 框架中加入对微服务的认证包括单点登录认证,来确保系统的安全,所以研究了Spring Cloud Security这个组件.在前面搭好的d ...

  7. 【微服务认证之一】统一登录认证

    首先我们要理解有状态请求和无状态请求. 这里的"状态"的意思是服务器端是否要保存用户状态. 比如说 cookie和session 是有状态的 因为我们要在服务器端保存session ...

  8. 微服务认证模式_微服务之“网关模式”

    定义 API网关是一个服务器,它是系统中的单个入口点,用户对API网关进行单一呼叫,然后API网关调用每个相关的微服务器.它类似于面向对象设计的Facade模式.API网关封装内部系统架构,并提供针对 ...

  9. CI Weekly #11 | 微服务场景下的自动化测试与持续部署

    又一周过去了,最近我们的工程师正在搞一个"大事情" --「flow.ci 配置文件」,稍微剧透一下,这个功能预计会在春节前上线.详情请大家关注 flow.ci Changelog ...

最新文章

  1. php的基本语法和数据类型
  2. Leetcode 109. 有序链表转换二叉搜索树 解题思路及C++实现
  3. LeetCode 143 重排链表-中等
  4. Today is weekend不是应该一定会输出吗
  5. [PHP] 日期与时间
  6. c语言指针易错情况,C语言/C++从入门到精通之指针易错点总结
  7. 适合海报设计的最佳字体
  8. MySQL学习8 - 数据的增删改
  9. NUC1474 Ants【水题】
  10. 安川焊接机器人做圆弧运动编程_安川MOTOMAN工业机器人编程与操作(3)
  11. 1024程序员节最新福利之2018最全大数据资料集合
  12. 如何交叉编译fio并移植到ARM、IOT上
  13. 如何注册Twitter,来学
  14. python随机森林模型简单股票涨跌预测
  15. VBA打开已加密的Excel文件
  16. 个人计算机cpu型号,终于知道如何看懂一个电脑CPU型号了!
  17. 【笔试】三七互娱笔试 web后端工程师
  18. 神经网络的学习方式-从网络传播到图卷积
  19. Status Ruby on Rails in China - Presentation Transcript
  20. UOS家庭版V21版BCM43142无线网卡网卡驱动重装

热门文章

  1. 2021-11-27 vue移动端卖座电影项目(二) 封装选项卡,引入iconfont,nowPlaying获取数据后写样式
  2. 扬长避短,做自己最擅长的事情
  3. 晶体谐振器的关键参数详解
  4. 在 npm 发布中文 API 初体验——中国历代纪元
  5. java小组的队名,小组队名和口号
  6. vue项目实现部分页面使用rem_vue 中使用rem布局
  7. uestc_retarded 模板
  8. Error: Exported bands must have compatible data types; found inconsistent types: Float64 and Float32
  9. 标签传播算法(LPA)
  10. 有哪些值得推荐的好用视频剪辑软件?