采用Spring Security AOuth2 和 JWT 的方式,避免每次请求都需要远程调度 Uaa 服务。采用Spring Security OAuth2 和 JWT 的方式,Uaa 服务只验证一次,返回JWT。返回的 JWT 包含了用户的所有信息,包括权限信息。

1.什么是JWT?#

JSON Web Token(JWT)是一种开放的标准(RFC 7519),JWT定义了一种紧凑且自包含的标准,该标准旨在将各个主体的信息包装为 JSON 对象。主体信息是通过数字签名进行加密和验证的。常使用 HMAC 算法或 RSA(公钥/私钥的非对称性加密)算法对JWT进行签名,安全性很高。

JWT 特点:

  • 紧凑型:数据体积小,可通过 POST 请求参数或 HTTP 请求头发送。
  • 自包含:JWT包含了主体的所有信息,避免了每个请求都需要向Uaa服务验证身份,降低了服务器的负载。

2.JWT的结构#

JWT结构:

  • Header(头)
  • Payload(有效载荷)
  • Signature(签名)

因此,JWT的通常格式是:xxxxx.yyyyy.zzzzz

(1)Header

Header 通常是由两部分组成:令牌的类型(即JWT)和使用的算法类型,如 HMAC、SHA256和RSA。例如:

Copy
{
"typ": "JWT",
"alg": "HS256"
}

将 Header 用 Base64 编码作为 JWT 的第一部分。

(2)Payload

这是 JWT 的第二部分,包含了用户的一些信息和Claim(声明、权利)。有3类型的 Claim:保留、公开和私人。

Copy
{
"sub": "123456789",
"name": "John Doe",
"admin": true
}

将 Payload 用 Base64 编码作为 JWT 的第一部分。

(3)Signature

要创建签名部分,需要将 Base64 编码后的 Header、Payload 和秘钥进行签名,一个典型的格式如下:

Copy
HMACSHA256(
base64UrlEncode(header) + '.' +
base64UrlEncode(payload),
secret
)

3.如何使用JWT#

认证流程图如下,客户端获取JWT后,以后每次请求都不需要再通过Uaa服务来判断该请求的用户以及该用户的权限。在微服务中,可以利用JWT实现单点登录。

4.案例工程架构#

三个工程:

  • eureka-server:注册服务中心,端口8761。这里不再演示搭建。
  • auth-service:负责授权,授权需要用户提供客户端的 clientId 和 password,以及授权用户的username和password。这些信息准备无误之后,auth-service 返回JWT,该 JWT 包含了用户的基本信息和权限点信息,并通过 RSA 加密。
  • user-service:作为资源服务,它的资源以及被保护起来了,需要相应的权限才能访问。user-service 服务得到用户请求的 JWT 后,先通过公钥解密JWT,得到该JWT对应的用户的信息和用户的权限信息,再判断该用户是否有权限访问该资源。

工程架构图:

5.构建auth-service工程#

1.新建Spring Boot工程,取名为 auth-service,其完整pom.xml文件为.

Copy
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-parent<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.5.3.RELEASE<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">relativePath</span>/&gt;</span> <span class="hljs-comment">&lt;!-- lookup parent from repository --&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.example<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>auth-service<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">name</span>&gt;</span>auth-service<span class="hljs-tag">&lt;/<span class="hljs-name">name</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">description</span>&gt;</span>Demo project for Spring Boot<span class="hljs-tag">&lt;/<span class="hljs-name">description</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">properties</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">java.version</span>&gt;</span>1.8<span class="hljs-tag">&lt;/<span class="hljs-name">java.version</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">spring-cloud.version</span>&gt;</span>Dalston.SR1<span class="hljs-tag">&lt;/<span class="hljs-name">spring-cloud.version</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">properties</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-eureka<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.security<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-security-jwt<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.security.oauth<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-security-oauth2<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-data-jpa<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>mysql<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>mysql-connector-java<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>test<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">dependencyManagement</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-dependencies<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>${spring-cloud.version}<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">type</span>&gt;</span>pom<span class="hljs-tag">&lt;/<span class="hljs-name">type</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>import<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependencyManagement</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span>
<span class="hljs-comment">&lt;!--防止jks文件被mavne编译导致不可用--&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>maven-resources-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">configuration</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">nonFilteredFileExtensions</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">nonFilteredFileExtension</span>&gt;</span>cert<span class="hljs-tag">&lt;/<span class="hljs-name">nonFilteredFileExtension</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">nonFilteredFileExtension</span>&gt;</span>jks<span class="hljs-tag">&lt;/<span class="hljs-name">nonFilteredFileExtension</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">nonFilteredFileExtensions</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">configuration</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span>

</project>

2.配置application.yml文件

Copy
spring:
application:
name: auth-service
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/spring-cloud-auth?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
username: root
password: 123456
jpa:
hibernate:
ddl-auto: update
show-sql: true
server:
port: 9999
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/

3.配置Spring Security

Copy
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
<span class="hljs-meta">@Override</span>
<span class="hljs-meta">@Bean</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> AuthenticationManager <span class="hljs-title">authenticationManagerBean</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>{
<span class="hljs-keyword">return</span> <span class="hljs-keyword">super</span>.authenticationManagerBean();
} <span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">configure</span><span class="hljs-params">(HttpSecurity http)</span> <span class="hljs-keyword">throws</span> Exception </span>{
http
.csrf().disable() <span class="hljs-comment">//关闭CSRF</span>
.exceptionHandling()
.authenticationEntryPoint((request, response, authException) -&gt; response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
.and()
.authorizeRequests()
.antMatchers(<span class="hljs-string">"/**"</span>).authenticated()
.and()
.httpBasic();
} <span class="hljs-meta">@Autowired</span>
UserServiceDetail userServiceDetail; <span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">configure</span><span class="hljs-params">(AuthenticationManagerBuilder auth)</span> <span class="hljs-keyword">throws</span> Exception </span>{
auth.userDetailsService(userServiceDetail)
.passwordEncoder(<span class="hljs-keyword">new</span> BCryptPasswordEncoder()); <span class="hljs-comment">//密码加密</span>
}

}

UserServiceDetail.java

Copy
@Service
public class UserServiceDetail implements UserDetailsService {
@Autowired
private UserDao userRepository;
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> UserDetails <span class="hljs-title">loadUserByUsername</span><span class="hljs-params">(String username)</span> <span class="hljs-keyword">throws</span> UsernameNotFoundException </span>{
<span class="hljs-keyword">return</span> userRepository.findByUsername(username);
}

}

UserDao.java

Copy
@Repository
public interface UserDao extends JpaRepository<User, Long> {
<span class="hljs-function">User <span class="hljs-title">findByUsername</span><span class="hljs-params">(String username)</span></span>;

}

User对象和上一篇文章的内容一样,需要实现UserDetails接口,Role对象需要实现GrantedAuthority接口.

Copy
@Entity
public class User implements UserDetails, Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
<span class="hljs-meta">@Column</span>(nullable = <span class="hljs-keyword">false</span>,  unique = <span class="hljs-keyword">true</span>)
<span class="hljs-keyword">private</span> String username; <span class="hljs-meta">@Column</span>
<span class="hljs-keyword">private</span> String password; <span class="hljs-meta">@ManyToMany</span>(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
<span class="hljs-meta">@JoinTable</span>(name = <span class="hljs-string">"user_role"</span>, joinColumns = <span class="hljs-meta">@JoinColumn</span>(name = <span class="hljs-string">"user_id"</span>, referencedColumnName = <span class="hljs-string">"id"</span>),
inverseJoinColumns = <span class="hljs-meta">@JoinColumn</span>(name = <span class="hljs-string">"role_id"</span>, referencedColumnName = <span class="hljs-string">"id"</span>))
<span class="hljs-keyword">private</span> List&lt;Role&gt; authorities; <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">User</span><span class="hljs-params">()</span> </span>{
} <span class="hljs-function"><span class="hljs-keyword">public</span> Long <span class="hljs-title">getId</span><span class="hljs-params">()</span> </span>{
<span class="hljs-keyword">return</span> id;
}
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setId</span><span class="hljs-params">(Long id)</span> </span>{
<span class="hljs-keyword">this</span>.id = id;
} <span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> Collection&lt;? extends GrantedAuthority&gt; getAuthorities() {
<span class="hljs-keyword">return</span> authorities;
} <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setAuthorities</span><span class="hljs-params">(List&lt;Role&gt; authorities)</span> </span>{
<span class="hljs-keyword">this</span>.authorities = authorities;
} <span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getUsername</span><span class="hljs-params">()</span> </span>{
<span class="hljs-keyword">return</span> username;
} <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setUsername</span><span class="hljs-params">(String username)</span> </span>{
<span class="hljs-keyword">this</span>.username = username;
} <span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getPassword</span><span class="hljs-params">()</span> </span>{
<span class="hljs-keyword">return</span> password;
} <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setPassword</span><span class="hljs-params">(String password)</span> </span>{
<span class="hljs-keyword">this</span>.password = password;
} <span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">isAccountNonExpired</span><span class="hljs-params">()</span> </span>{
<span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
} <span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">isAccountNonLocked</span><span class="hljs-params">()</span> </span>{
<span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
} <span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">isCredentialsNonExpired</span><span class="hljs-params">()</span> </span>{
<span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
} <span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">isEnabled</span><span class="hljs-params">()</span> </span>{
<span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
}

}

Copy
@Entity
public class Role implements GrantedAuthority { @Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; @Column(nullable = false)
private String name; public Long getId() {
return id;
} public void setId(Long id) {
this.id = id;
} @Override
public String getAuthority() {
return name;
} public void setName(String name) {
this.name = name;
} @Override
public String toString() {
return name;
}
}

4.配置 Authorization Server

在 OAuth2Config 这个类中配置 AuthorizationServer,其代码如下:

Copy
@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory() //将客户端的信息存储在内存中
.withClient("user-service") //创建了一个Client为"user-service"的客户端
.secret("123456")
.scopes("service") //客户端的域
.authorizedGrantTypes("refresh_token", "password") //配置类验证类型为 refresh_token和password
.accessTokenValiditySeconds(12*300); //5min过期
}
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">configure</span><span class="hljs-params">(AuthorizationServerEndpointsConfigurer endpoints)</span> <span class="hljs-keyword">throws</span> Exception </span>{
endpoints.tokenStore(tokenStore()).tokenEnhancer(jwtTokenEnhancer()).authenticationManager(authenticationManager);
} <span class="hljs-meta">@Autowired</span>
<span class="hljs-meta">@Qualifier</span>(<span class="hljs-string">"authenticationManagerBean"</span>)
<span class="hljs-keyword">private</span> AuthenticationManager authenticationManager; <span class="hljs-meta">@Bean</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> TokenStore <span class="hljs-title">tokenStore</span><span class="hljs-params">()</span> </span>{
<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> JwtTokenStore(jwtTokenEnhancer());
} <span class="hljs-meta">@Bean</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> JwtAccessTokenConverter <span class="hljs-title">jwtTokenEnhancer</span><span class="hljs-params">()</span> </span>{
<span class="hljs-comment">//注意此处需要相应的jks文件</span>
KeyStoreKeyFactory keyStoreKeyFactory = <span class="hljs-keyword">new</span> KeyStoreKeyFactory(<span class="hljs-keyword">new</span> ClassPathResource(<span class="hljs-string">"fzp-jwt.jks"</span>), <span class="hljs-string">"fzp123"</span>.toCharArray());
JwtAccessTokenConverter converter = <span class="hljs-keyword">new</span> JwtAccessTokenConverter();
converter.setKeyPair(keyStoreKeyFactory.getKeyPair(<span class="hljs-string">"fzp-jwt"</span>));
<span class="hljs-keyword">return</span> converter;
}

}

5.生成 jks 文件

配置 JwtTokenStore 时需要使用 jks 文件作为 Token 加密的秘钥。

jks 文件需要Java keytool工具,保证Java环境变量没问题,打开计算机终端,输入命令:

keytool -genkeypair -alias fzp-jwt -validity 3650 -keyalg RSA -dname "CN=jwt,OU=jtw,O=jwt,L=zurich,S=zurich,C=CH" -keypass fzp123 -keystore fzp-jwt.jks -storepass fzp123

解释,-alias 选项为别名,-keypass 和 -storepass 为密码选项,-validity 为配置jks文件过期时间(单位:天)。

获取的 jks 文件作为私钥,只允许 Uaa 服务持有,并用作加密 JWT。也就是把生成的 jks 文件放到 auth-service 工程的resource目录下。那么 user-service 这样的资源服务,是如何解密 JWT 的呢?这时就需要使用 jks 文件的公钥。获取 jks 文件的公钥命令如下:

keytool -list -rfc --keystore fzp-jwt.jks | openssl x509 -inform pem -pubkey

这个命令要求你的计算机上安装了openSSL(下载地址),然后手动把安装的openssl.exe所在目录配置到环境变量。

输入密码fzp123后,显示的信息很多,我们只提取 PUBLIC KEY,即如下所示:

-----BEGIN PUBLIC KEY-----

MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlCFiWbZXIb5kwEaHjW+/

7J4b+KzXZffRl5RJ9rAMgfRXHqGG8RM2Dlf95JwTXzerY6igUq7FVgFjnPbexVt3

vKKyjdy2gBuOaXqaYJEZSfuKCNN/WbOF8e7ny4fLMFilbhpzoqkSHiR+nAHLkYct

OnOKMPK1SwmvkNMn3aTEJHhxGh1RlWbMAAQ+QLI2D7zCzQ7Uh3F+Kw0pd2gBYd8W

+DKTn1Tprugdykirr6u0p66yK5f1T9O+LEaJa8FjtLF66siBdGRaNYMExNi21lJk

i5dD3ViVBIVKi9ZaTsK9Sxa3dOX1aE5Zd5A9cPsBIZ12spYgemfj6DjOw6lk7jkG

9QIDAQAB

-----END PUBLIC KEY-----

新建一个 public.cert 文件,将上面的公钥信息复制到 public.cert 文件中并保存。并将文件放到 user-service 等资源服务的resources目录下。到目前为止,Uaa 服务已经搭建完毕。

需要注意的是,Maven 在项目编译时,可能会将 jks 文件编译,导致 jks 文件乱码,最后不可用。需要在工程的 pom 文件中添加以下内容:

Copy
<!--防止jks文件被mavne编译导致不可用-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>cert</nonFilteredFileExtension>
<nonFilteredFileExtension>jks</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>

最后,别忘了在启动类注解@EnableEurekaClient开启服务注册.

Copy
@SpringBootApplication
@EnableEurekaClient
public class AuthServiceApplication {
public static void main(String[] args) {
SpringApplication.run(AuthServiceApplication.class, args);
}
}

6.构建user-service资源服务#

1.新建Spring Boot工程,取名为user-service,其完整pom.xml文件:

Copy
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.example<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>user-service<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">name</span>&gt;</span>user-service<span class="hljs-tag">&lt;/<span class="hljs-name">name</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">description</span>&gt;</span>Demo project for Spring Boot<span class="hljs-tag">&lt;/<span class="hljs-name">description</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">properties</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">java.version</span>&gt;</span>1.8<span class="hljs-tag">&lt;/<span class="hljs-name">java.version</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">spring-cloud.version</span>&gt;</span>Dalston.SR1<span class="hljs-tag">&lt;/<span class="hljs-name">spring-cloud.version</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">properties</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-eureka<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.security.oauth<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-security-oauth2<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-data-jpa<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>mysql<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>mysql-connector-java<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-hystrix<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-feign<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>test<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">dependencyManagement</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-dependencies<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>${spring-cloud.version}<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">type</span>&gt;</span>pom<span class="hljs-tag">&lt;/<span class="hljs-name">type</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>import<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependencyManagement</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span>

</project>

2.配置文件application.yml

在工程的配置文件application.yml中,配置程序名为 user-service,端口号为 9090,另外,需要配置 feign.hystrix.enable 为true,即开启 Feign 的 Hystrix 功能。完整的配置代码如下:

Copy
server:
port: 9090
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: user-service
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/spring-cloud-auth?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
username: root
password: 123456
jpa:
hibernate:
ddl-auto: update
show-sql: true
feign:
hystrix:
enabled: true

3.配置Resource Server

在配置Resource Server之前,需要注入 JwtTokenStore 类型的 Bean。

Copy
@Configuration
public class JwtConfig {
@Autowired
JwtAccessTokenConverter jwtAccessTokenConverter;
<span class="hljs-meta">@Bean</span>
<span class="hljs-meta">@Qualifier</span>(<span class="hljs-string">"tokenStore"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> TokenStore <span class="hljs-title">tokenStore</span><span class="hljs-params">()</span> </span>{
<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> JwtTokenStore(jwtAccessTokenConverter);
} <span class="hljs-meta">@Bean</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> JwtAccessTokenConverter <span class="hljs-title">jwtTokenEnhancer</span><span class="hljs-params">()</span> </span>{
<span class="hljs-comment">//用作 JWT 转换器</span>
JwtAccessTokenConverter converter = <span class="hljs-keyword">new</span> JwtAccessTokenConverter();
Resource resource = <span class="hljs-keyword">new</span> ClassPathResource(<span class="hljs-string">"public.cert"</span>);
String publicKey ;
<span class="hljs-keyword">try</span> {
publicKey = <span class="hljs-keyword">new</span> String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
} <span class="hljs-keyword">catch</span> (IOException e) {
<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(e);
}
converter.setVerifierKey(publicKey); <span class="hljs-comment">//设置公钥</span>
<span class="hljs-keyword">return</span> converter;
}

}

然后配置 Resource Server

Copy
@Configuration
@EnableResourceServer //开启Resource Server功能
public class ResourceServerConfig extends ResourceServerConfigurerAdapter{
@Autowired
TokenStore tokenStore;
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">configure</span><span class="hljs-params">(HttpSecurity http)</span> <span class="hljs-keyword">throws</span> Exception </span>{
http
.csrf().disable()
.authorizeRequests()
.antMatchers(<span class="hljs-string">"/user/login"</span>,<span class="hljs-string">"/user/register"</span>).permitAll()
.antMatchers(<span class="hljs-string">"/**"</span>).authenticated(); } <span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">configure</span><span class="hljs-params">(ResourceServerSecurityConfigurer resources)</span> <span class="hljs-keyword">throws</span> Exception </span>{
resources.tokenStore(tokenStore);
}

}

4.新建一个配置类 GlobalMethodSecurityConfig,在此类中通过 @EnableGlobalMethodSecurity(prePostEnabled = true)注解开启方法级别的安全验证。

Copy
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class GlobalMethodSecurityConfig { }

5.编写用户注册接口

拷贝auth-service工程的User.java、Role.java 和 UserDao.java 到本工程。

在 Service 层的 UserService 写一个插入用户的方法,代码如下

Copy
@Service
public class UserServiceDetail {
<span class="hljs-meta">@Autowired</span>
<span class="hljs-keyword">private</span> UserDao userRepository; <span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">insertUser</span><span class="hljs-params">(String username,String password)</span></span>{
User user=<span class="hljs-keyword">new</span> User();
user.setUsername(username);
user.setPassword(BPwdEncoderUtil.BCryptPassword(password));
<span class="hljs-keyword">return</span> userRepository.save(user);
}

}

BPwdEncoderUtil工具类

Copy
public class BPwdEncoderUtil {
<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> BCryptPasswordEncoder encoder = <span class="hljs-keyword">new</span> BCryptPasswordEncoder();

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> String  <span class="hljs-title">BCryptPassword</span><span class="hljs-params">(String password)</span></span>{
<span class="hljs-keyword">return</span> encoder.encode(password);
} <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">matches</span><span class="hljs-params">(CharSequence rawPassword, String encodedPassword)</span></span>{
<span class="hljs-keyword">return</span> encoder.matches(rawPassword,encodedPassword);
}

}

在 Web 层,在 Controller 中写一个注册的 API 接口 “/user/register”,代码如下

Copy
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserServiceDetail userServiceDetail;
<span class="hljs-meta">@PostMapping</span>(<span class="hljs-string">"/register"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">postUser</span><span class="hljs-params">(@RequestParam(<span class="hljs-string">"username"</span>)</span> String username , @<span class="hljs-title">RequestParam</span><span class="hljs-params">(<span class="hljs-string">"password"</span>)</span> String password)</span>{
<span class="hljs-comment">//参数判断,省略</span>
<span class="hljs-keyword">return</span> userServiceDetail.insertUser(username,password);
}

}

6.编写用户登录接口

在Service层,在 UserServiceDetail 中添加一个 login(登录)方法,代码如下:

Copy
@Service
public class UserServiceDetail {
<span class="hljs-meta">@Autowired</span>
<span class="hljs-keyword">private</span> AuthServiceClient client; <span class="hljs-function"><span class="hljs-keyword">public</span> UserLoginDTO <span class="hljs-title">login</span><span class="hljs-params">(String username, String password)</span></span>{
User user=userRepository.findByUsername(username);
<span class="hljs-keyword">if</span> (<span class="hljs-keyword">null</span> == user) {
<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> UserLoginException(<span class="hljs-string">"error username"</span>);
}
<span class="hljs-keyword">if</span>(!BPwdEncoderUtil.matches(password,user.getPassword())){
<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> UserLoginException(<span class="hljs-string">"error password"</span>);
}
<span class="hljs-comment">// 获取token</span>
JWT jwt=client.getToken(<span class="hljs-string">"Basic dXNlci1zZXJ2aWNlOjEyMzQ1Ng=="</span>,<span class="hljs-string">"password"</span>,username,password);
<span class="hljs-comment">// 获得用户菜单</span>
<span class="hljs-keyword">if</span>(jwt==<span class="hljs-keyword">null</span>){
<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> UserLoginException(<span class="hljs-string">"error internal"</span>);
}
UserLoginDTO userLoginDTO=<span class="hljs-keyword">new</span> UserLoginDTO();
userLoginDTO.setJwt(jwt);
userLoginDTO.setUser(user);
<span class="hljs-keyword">return</span> userLoginDTO; }

}

AuthServiceClient 通过向 auth-service 服务远程调用“/oauth/token” API接口,获取 JWT。在 "/oauth/token" API 接口,获取JWT。在“/oauth/token”API接口中需要在请求头传入 Authorization 信息,并需要传请求参数认证类型 grant_type、用户名 username 和密码 password,代码如下:

Copy
@FeignClient(value = "auth-service",fallback =AuthServiceHystrix.class )
public interface AuthServiceClient {
<span class="hljs-meta">@PostMapping</span>(value = <span class="hljs-string">"/oauth/token"</span>)
<span class="hljs-function">JWT <span class="hljs-title">getToken</span><span class="hljs-params">(@RequestHeader(value = <span class="hljs-string">"Authorization"</span>)</span> String authorization, @<span class="hljs-title">RequestParam</span><span class="hljs-params">(<span class="hljs-string">"grant_type"</span>)</span> String type,
@<span class="hljs-title">RequestParam</span><span class="hljs-params">(<span class="hljs-string">"username"</span>)</span> String username, @<span class="hljs-title">RequestParam</span><span class="hljs-params">(<span class="hljs-string">"password"</span>)</span> String password)</span>;

}

其中,AuthServiceHystrix 为AuthServiceClient 的熔断器,代码如下:

Copy
@Component
public class AuthServiceHystrix implements AuthServiceClient {
@Override
public JWT getToken(String authorization, String type, String username, String password) {
return null;
}
}

JWT 为一个 JavaBean,它包含了 access_token、token_type 和 refresh_token 等信息,代码如下:

Copy
public class JWT {
private String access_token;
private String token_type;
private String refresh_token;
private int expires_in;
private String scope;
private String jti;
//getter setter

UserLoginDTO 包含了一个 User 和一个 JWT 对象,用于返回数据的实体:

Copy
public class UserLoginDTO {
private JWT jwt;
private User user;
//setter getter
}

登录异常类 UserLoginException

Copy
public class UserLoginException extends RuntimeException{
public UserLoginException(String message) {
super(message);
}
}

统一异常处理

Copy
@ControllerAdvice
@ResponseBody
public class ExceptionHandle {
@ExceptionHandler(UserLoginException.class)
public ResponseEntity<String> handleException(Exception e) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ResponseEntity(e.getMessage(), HttpStatus.OK);
}

}

在web层的 UserController 类补充一个登录的API接口“/user/login”.

Copy
@PostMapping("/login")
public UserLoginDTO login(@RequestParam("username") String username , @RequestParam("password") String password){
//参数判断,省略
return userServiceDetail.login(username,password);
}

为了测试权限,再补充一个"/foo"接口,该接口需要“ROLE_ADMIN”权限.

Copy
@RequestMapping(value = "/foo", method = RequestMethod.GET)
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
public String getFoo() {
return "i'm foo, " + UUID.randomUUID().toString();
}

最后,在启动类注解开启Feign:

Copy
@SpringBootApplication
@EnableFeignClients
@EnableEurekaClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}

7.启动工程,开始测试#

经过千辛万苦,终于搭建了一个Demo工程,现在开始依次启动 eureka-server、auth-service 和 user-service工程。这里我们使用PostMan测试编写的接口。

1.注册用户

2.登录获取Token

3.访问/user/foo

复制 access_token到 Header头部,发起GET请求。

Copy
"Authorization":"Bearer {access_token}"

因为没有权限,访问被拒绝,我们手动在数据库添加"ROLE_ADMIN"权限,并与该用户关联。重新登录并获取Token,重新请求“/user/foo”接口

总结#

在本案例中,用户通过登录接口来获取授权服务的Token 。用户获取Token 成功后,在以后每次访问资源服务的请求中都需要携带该Token 。资源服务通过公钥解密Token ,解密成功后可以获取用户信息和权限信息,从而判断该Token 所对应的用户是谁, 具有什么权限。

这个架构的优点在于,一次获取Token , 多次使用,不再每次询问Uaa 服务该Token 所对应的用户信息和用户的权限信息。这个架构也有缺点,例如一旦用户的权限发生了改变, 该Token 中存储的权限信息并没有改变, 需要重新登录获取新的Token 。就算重新获取了Token,如果原来的Token 没有过期,仍然是可以使用的,所以需要根据具体的业务场景来设置Token的过期时间。一种改进方式是将登录成功后获取的Token 缓存在网关上,如果用户的权限更改,将网关上缓存的Token 删除。当请求经过网关,判断请求的Token 在缓存中是否存在,如果缓存中不存在该Token ,则提示用户重新登录。

参考:《深入理解Spring Cloud与微服务构建》方志朋

JWT返回null问题解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("client")
                .redirectUris("http://www.baidu.com")
                //此处的scopes是无用的,可以随意设置
                .scopes("all","read", "write")
                .secret(passwordEncoder.encode("secret"))//这块就是解决办法,原本secret换成passwordEncoder.encode("secret")
                .authorizedGrantTypes("password", "authorization_code", "refresh_token")
                //有效期时间
//                .accessTokenValiditySeconds(accessTokenValiditySeconds)
                //刷新事件
//                .refreshTokenValiditySeconds(refreshTokenValiditySeconds)
                .and()
                .withClient("webapp")
                .scopes("xx")
                .authorizedGrantTypes("implicit");
    }

使用Spring Cloud OAuth2和JWT保护微服务的更多相关文章

  1. SpringCloud(10)使用Spring Cloud OAuth2和JWT保护微服务

    采用Spring Security AOuth2 和 JWT 的方式,避免每次请求都需要远程调度 Uaa 服务.采用Spring Security OAuth2 和 JWT 的方式,Uaa 服务只验证 ...

  2. 基于Spring Cloud和Netflix OSS构建微服务,Part 2

    在上一篇文章中,我们已使用Spring Cloud和Netflix OSS中的核心组件,如Eureka.Ribbon和Zuul,部分实现了操作模型(operations model),允许单独部署的微 ...

  3. 今天介绍一下自己的开源项目,一款以spring cloud alibaba为核心的微服务架构项目,为给企业与个人提供一个零开发基础的微服务架构。

    LaoCat-Spring-Cloud-Scaffold 一款以spring cloud alibab 为核心的微服务框架,主要目标为了提升自己的相关技术,也为了给企业与个人提供一个零开发基础的微服务 ...

  4. 最新最简洁Spring Cloud Oauth2.0 Jwt 的Security方式

    因为Spring Cloud 2020.0.0和Spring Boot2.4.1版本升级比较大,所以把我接入过程中的一些需要注意的地方告诉大家 我使用的版本是Spring boot 2.4.1+Spr ...

  5. Spring Cloud(1):微服务简介

    架构的演进: 1.十年前:用户->单一服务器->单一数据库(支持十万级用户) 2.五年前:用户->负载均衡器->多台服务器->缓存集群->主从数据库(支持百万级用户 ...

  6. 基于Spring Cloud和Netflix OSS 构建微服务-Part 1

    前一篇文章<微服务操作模型>中,我们定义了微服务使用的操作模型.这篇文章中,我们将开始使用Spring Cloud和Netflix OSS实现这一模型,包含核心部分:服务发现(Servic ...

  7. spring cloud 入门,看一个微服务框架的「五脏六腑」

    Spring Cloud 是一个基于 Spring Boot 实现的微服务框架,它包含了实现微服务架构所需的各种组件. 注:Spring Boot 简单理解就是简化 Spring 项目的搭建.配置.组 ...

  8. 使用 Spring Cloud Stream 构建消息驱动微服务

    相关源码: spring cloud demo 微服务的目的: 松耦合 事件驱动的优势:高度解耦 Spring Cloud Stream 的几个概念 Spring Cloud Stream is a ...

  9. spring cloud(学习笔记)微服务启动错误(1)

    今天下午在启动spring cloud微服务的时候,报了这个错误: Error starting ApplicationContext. To display the auto-configurati ...

随机推荐

  1. At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger fo

    一.文章前言 本文是亲测有效解决At least one JAR was scanned for TLDs yet contained no TLDs问题,绝对不是为了积分随便粘贴复制然后压根都没有用 ...

  2. JVM内存空间划分与作用

    虚拟机栈:Stack Fame 栈桢 程序计数器(Program Counter): 本地方法栈:主要用于处理本地方法 堆(Heap): JVM管理的最大一块内存空间 方法区(Method Area) ...

  3. vim脚本判断操作系统

    Linux 和 Windows 通用配置 其实在配置文件中是可以通过逻辑代码判断平台做条件处理的,这样就可以实现一个配置文件两个个平台下共用了,判断逻辑如下: " ============= ...

  4. Docker 容器日志分析

    查看容器日志 先使用  docker run -it --rm -d -p 80:80 nginx:1.15.8-alpine 命令启动一个nginx容器.如果没有异常,会得到容器ID如  d2408 ...

  5. TypeScript泛型类 - 把类作为参数类型的泛型类

    /* TypeScript泛型类 - 把类作为参数类型的泛型类 */ /* 泛类:泛型可以帮助我们避免重复的代码以及对不特定数据类型的支持(类型校验),下面我们看看把类当做参数的泛型类 1.定义个类 ...

  6. 代替ESXI的虚拟机解决方案proxmox

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/matengbing/article/de ...

  7. postgresql 所有聚合函数整理

    SELECT DISTINCT(proname) FROM pg_proc WHERE proisagg order by proname 查所有 SELECT * FROM pg_proc WHER ...

  8. DEBUG技巧里的问题1 双击某个变量不能显示

    DEBUG模式  双击 ls_return-type 变量不能显示,提示警告消息 好像说明的不是这个问题, 把字段复制到右边的变量框里可以显示 这个确实有点奇怪了

  9. 今天被这个BDE错误搞了半天,不过终于好了,分享一下

    今天正编译程序时,突然就报了这个错误出来,重启电脑都没用,多亏网上高手指教,先把解决方案列于下,供受此累得朋友查阅,自己也留底供查找:"Shared memory conflict ($21 ...

  10. array_fill 填充数组内容

    <?php $a = array_fill(, , 'banana'); $b = array_fill(-, , 'pear'); print_r($a); print_r($b) Array ...