授权服务器主要是提供用户认证、授权、颁发令牌等功能,而资源服务器主要是保护用户资源。授权服务器先给合法的用户颁发令牌,用户再使用获得的令牌到资源服务器申请受保护的用户资源,而资源服务器接收到用户的请求后,会先验证用户的令牌,只有令牌合法才会把用户请求的资源返回给用户,否则拒绝返回。
资源服务器验证用户的令牌可以有多种方式,既可以把用户的令牌拿到授权服务器处进行验证,也可以自己验证。只有授权服务器使用非对称加密令牌或者使用对称加密令牌的方式,且资源服务器知道授权服务器用于加密令牌的密钥所对应的公钥时,资源服务器才能自己验证令牌。本示例中,授权服务器使用对称加密令牌的方式加密令牌,资源服务器自己验证令牌。
OAuth2授权服务器和四种授权方式这篇文章介绍了授权服务器以及基于授权码授权、密码授权、简化授权、客户端授权等四种授权方式的实现,因此,本文不再重复介绍授权服务器相关的内容,而主要是介绍资源服务器的实现。本文的资源服务器使用方法级的权限控制来保护用户资源。
本文主要是介绍资源服务器的实现,所以省略了授权服务器工程,同时也省略了eureka服务注册中心工程和common-model公共实体类工程。
创建一个名为user-resource-server的spring cloud微服务工程(用户信息资源服务器),项目结构如下图所示:
版本说明:
要想启用资源服务器,需要实现一个继承了ResourceServerConfigurerAdapter类的配置类,并且在实现的配置类中使用@EnableResourceServer注解。本文的资源服务器配置类AuthorizationServerConfig代码如下所示:
package com.user.resource.server.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
/**
* OAuth的资源服务器配置
* 注意:必须继承ResourceServerConfigurerAdapter类,并使用@Configuration和@EnableResourceServer注解
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
/**
* 微服务的Http安全配置
*/
@Override
public void configure(HttpSecurity http) throws Exception {
/**
* 不允许跨域请求,/getQQUserAuth请求不需要认证,其它请求都需要认证
*/
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/getQQUserAuth").permitAll()
.anyRequest()
.authenticated();
}
/**
* 资源服务器的安全配置
*/
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
/**
* 设置令牌的存储方式
*/
resources.tokenStore(tokenStore());
}
/**
* 创建令牌存储对象
*/
@Bean
public TokenStore tokenStore() {
/**
* 使用JwtTokenStore时,必须注入一个JwtAccessTokenConverter,用于解析JWT令牌
*/
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* 创建JWT令牌转换器
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
/**
* 设置JWT令牌的签名key
*/
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("signingKey");
return converter;
}
}
资源服务器配置类中主要是设置安全配置,以及设置令牌存储和验证的配置。
获取用户认证信息的接口不需要授杼就可以直接访问,获取用户详细信息的接口需要授权才能访问,而且只允许授权用户查询自己的详细信息。
package com.user.resource.server.controller;
import com.common.model.domain.QQUser;
import com.common.model.ext.QQUserAuth;
import com.common.model.request.QQUserQuery;
import com.common.model.response.ResponseResult;
import com.user.resource.server.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
* 获取用户信息接口入口
*/
@RestController
public class UserController {
@Autowired
UserService userService;
/**
* 根据用户名获取认证用户信息(不要求用户已认证,即不要求有访问令牌)
*/
@PostMapping("/getQQUserAuth")
public ResponseResult getQQUserAuth(@RequestBody QQUserQuery query) {
QQUserAuth user = null;
if(query == null) {
return ResponseResult.getFailureResult("300", "没有接收到用户信息");
} else if(!StringUtils.isEmpty(query.getUsername())) {
user = userService.getQQUserAuthByUsername(query.getUsername());
}
if(user == null) {
return ResponseResult.getFailureResult("300", "没有查询到该用户信息");
} else {
return ResponseResult.getSuccessResult().put("QQUserAuth", user);
}
}
/**
* 已认证用户查询自己的用户信息,不允许查询他人的用户信息(要求用户已认证,即要求有合法的访问令牌)
*/
@PostMapping("/api/getQQUser")
@PreAuthorize("principal.equals(#query.getUsername())")
public ResponseResult getQQUser(@RequestBody QQUserQuery query) {
QQUser user = null;
if(query == null) {
return ResponseResult.getFailureResult("300", "没有接收到用户信息");
} else if(!StringUtils.isEmpty(query.getUsername())) {
user = userService.getQQUserByUsername(query.getUsername());
}
if(user == null) {
return ResponseResult.getFailureResult("300", "没有获取到该用户信息");
} else {
return ResponseResult.getSuccessResult().put(user);
}
}
}
package com.user.resource.server.service;
import com.common.model.domain.QQUser;
import com.common.model.ext.QQUserAuth;
/**
* 获取用户信息接口
*/
public interface UserService {
/**
* 根据用户名获取认证用户信息
*/
QQUserAuth getQQUserAuthByUsername(String username);
/**
* 根据用户名获取用户详细信息
*/
QQUser getQQUserByUsername(String username);
}
package com.user.resource.server.service.impl;
import com.common.model.domain.QQUser;
import com.common.model.ext.QQUserAuth;
import com.user.resource.server.service.UserService;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* 获取用户信息接口实现类
*/
@Service
public class UserServiceImpl implements UserService {
/**
* 根据用户名获取认证用户信息
*/
@Override
public QQUserAuth getQQUserAuthByUsername(String username) {
if (username.equals("admin")) {
QQUserAuth user = new QQUserAuth();
user.setId(1L);
user.setUsername(username);
/**
* 密码为123(通过BCryptPasswordEncoderl加密后的密文)
*/
user.setPassword("$2a$10$U6g06YmMfRJXcNfLP28TR.xy21u1A5kIeY/OZMKBDVMbn7PGJiaZS");
List<String> roles = new ArrayList<>();
roles.add("ROLE_USER");
user.setRoles(roles);
return user;
} else if (username.equals("employee")) {
QQUserAuth user = new QQUserAuth();
user.setId(2L);
user.setUsername(username);
/**
* 密码为123(通过BCryptPasswordEncoderl加密后的密文)
*/
user.setPassword("$2a$10$U6g06YmMfRJXcNfLP28TR.xy21u1A5kIeY/OZMKBDVMbn7PGJiaZS");
List<String> roles = new ArrayList<>();
roles.add("ROLE_EMPLOYEE");
user.setRoles(roles);
return user;
}
return null;
}
/**
* 根据用户名获取用户详细信息
*/
@Override
public QQUser getQQUserByUsername(String username) {
if (username.equals("admin")) {
QQUser user = new QQUser();
user.setId(1L);
user.setUsername(username);
/**
* 密码为123(通过BCryptPasswordEncoderl加密后的密文)
*/
user.setPassword("$2a$10$U6g06YmMfRJXcNfLP28TR.xy21u1A5kIeY/OZMKBDVMbn7PGJiaZS");
user.setNickname("管理员");
user.setName("张三");
user.setGender(true);
user.setCity("China");
user.setCity("WuHan");
user.setMobile("17371580000");
user.setEmail("[email protected]");
return user;
} else if (username.equals("employee")) {
QQUser user = new QQUser();
user.setId(2L);
user.setUsername(username);
/**
* 密码为123(通过BCryptPasswordEncoderl加密后的密文)
*/
user.setPassword("$2a$10$U6g06YmMfRJXcNfLP28TR.xy21u1A5kIeY/OZMKBDVMbn7PGJiaZS");
user.setNickname("普通员工");
user.setName("李四");
user.setGender(true);
user.setCity("China");
user.setCity("WuHan");
user.setMobile("17371580000");
user.setEmail("[email protected]");
return user;
}
return null;
}
}
package com.user.resource.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.ComponentScan;
/**
* 用户信息资源服务器微服务启动类
*/
@ComponentScan("com.user.resource.server.*")
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableEurekaClient
@SpringBootApplication
public class UserResourceServerApplication {
public static void main(String[] args) {
SpringApplication.run(UserResourceServerApplication.class, args);
}
}
pom.xml依赖配置文件如下所示:
<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.0modelVersion>
<groupId>com.resource.servergroupId>
<artifactId>user-resource-serverartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>user-resource-servername>
<description>用户资源服务description>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.3.RELEASEversion>
<relativePath/>
parent>
<properties>
<java.version>1.8java.version>
<spring-cloud.version>Greenwich.SR1spring-cloud.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-oauth2artifactId>
dependency>
<dependency>
<groupId>com.common.modelgroupId>
<artifactId>common-modelartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
application.yml配置文件如下所示:
server:
port: 15000
spring:
application:
name: @pom.artifactId@
eureka:
client:
serviceUrl:
defaultZone: http://localhost:10000/eureka/
instance:
prefer-ip-address: true
instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
分别启动Eureka服务注册中心工程、auth-server授权服务器工程、user-resource-server用户资源服务器工程。
要想从用户资源服务器中获取受保护的用户资源(本文中是指用户的详细信息),需要先到授权服务器处进行认证、授权并获得访问令牌,然后才能使用访问令牌到用户资源服务器处请求受保护的用户资源。
从授权服务器获取令牌的方法请见OAuth2授权服务器和四种授权方式这篇文章。
本文使用用户名admin和密码123进行认证和授权,并基于密码授权方式获取令牌。获取的令牌如下图所示:
本文中用户的详细信息属于受保护的用户资源,用户想要获取该资源,就必须要求用户已认证(即有合法的访问令牌),而且,本文中对获取用户详细信息这个方法设置了权限,只允许认证用户查询自己的用户详细信息。因此,本文从三个方面来测试:
使用访问令牌(access_token)到资源服务器请求受保护的用户资源时,需要以Bearer Token类型的认证头的方式把访问令牌添加到请求中。
1. 使用postman工具来发送请求,首先在不添加访问令牌的情况下直接查询admin用户的详细信息,请求详情如下所示:
请求结果如下图所示,提示该请求因为没有授权(即访问令牌)而被拒绝访问:
2. 使用postman工具来发送请求,在添加访问令牌的情况下查询admin用户的详细信息,请求详情如下所示:
请求结果如下图所示,成功的从资源服务器中admin用户的详细信息:
3. 使用postman工具来发送请求,在添加访问令牌的情况下查询employee用户的详细信息,请求详情如下所示:
请求结果如下图所示,提示该请求因为权限而被拒绝访问:
通过上面的三次测试,说明用户资源服务器确实起到了保护用户资源的作用,只有用户有合法的访问令牌时才允许用户获取受保护的用户资源。
本文简要的介绍了资源服务器的实现方法和使用方法,但是实现的资源服务器还十分基础。关于授权服务器、用户认证、权限控制等相关的内容可以查阅以下几篇文章:
OAuth2授权服务器和四种授权方式 这篇文章介绍了授权服务器和四种授权方式的配置与使用方法。
Spring Security用户认证和权限控制(默认实现)这篇文章介绍了方法级别的权限控制。
Spring Security用户认证和权限控制(自定义实现)这篇文章介绍了URL级别的权限控制。
如果觉得本文对您有帮助,请关注博主的微信公众号,会经常分享一些Java和大数据方面的技术案例!