spring-cloud-starter-oauth2包含了 spring-cloud-starter-security,所以不用再单独引入了。之所以引入 redis 包,是因为下面会介绍一种用 redis 存储 token 的方式。
2、配置好 application.yml
将项目基本配置设置好,并加入有关 redis 的配置,稍后会用到。
spring:
application:
name: auth-server
redis:
database: 2
host: localhost
port: 32768
password: 1qaz@WSX
jedis:
pool:
max-active: 8
max-idle: 8
min-idle: 0
timeout: 100ms
server:
port: 6001
management:
endpoint:
health:
enabled: true
3、spring security 基础配置
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
允许匿名访问所有接口 主要是 oauth 接口
@param http
@throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(“/**”).permitAll();
}
}
使用@EnableWebSecurity注解修饰,并继承自WebSecurityConfigurerAdapter类。
这个类的重点就是声明 PasswordEncoder 和 AuthenticationManager两个 Bean。稍后会用到。其中 BCryptPasswordEncoder是一个密码加密工具类,它可以实现不可逆的加密,AuthenticationManager是为了实现 OAuth2 的 password 模式必须要指定的授权管理 Bean。
4、实现 UserDetailsService
如果你之前用过 Security 的话,那肯定对这个类很熟悉,它是实现用户身份验证的一种方式,也是最简单方便的一种。另外还有结合 AuthenticationProvider的方式,有机会讲 Security 的时候再展开来讲吧。
UserDetailsService的核心就是 loadUserByUsername方法,它要接收一个字符串参数,也就是传过来的用户名,返回一个 UserDetails对象。
@Slf4j
@Component(value = “kiteUserDetailsService”)
public class KiteUserDetailsService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info(“usernameis:” + username);
// 查询数据库操作
if(!username.equals(“admin”)){
throw new UsernameNotFoundException(“the user is not found”);
}else{
// 用户角色也应在数据库中获取
String role = “ROLE_ADMIN”;
List authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(role));
// 线上环境应该通过用户名查询数据库获取加密后的密码
String password = passwordEncoder.encode(“123456”);
return new org.springframework.security.core.userdetails.User(username,password, authorities);
}
}
}
这里为了做演示,把用户名、密码和所属角色都写在代码里了,正式环境中,这里应该是从数据库或者其他地方根据用户名将加密后的密码及所属角色查出来的。账号 admin ,密码 123456,稍后在换取 token 的时候会用到。并且给这个用户设置 “ROLE_ADMIN” 角色。
5、OAuth2 配置文件
创建一个配置文件继承自 AuthorizationServerConfigurerAdapter.
@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
@Autowired
public PasswordEncoder passwordEncoder;
@Autowired
public UserDetailsService kiteUserDetailsService;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private TokenStore redisTokenStore;
@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
/**
*/
endpoints.authenticationManager(authenticationManager)
.userDetailsService(kiteUserDetailsService)
.tokenStore(redisTokenStore);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(“order-client”)
.secret(passwordEncoder.encode(“order-secret-8888”))
.authorizedGrantTypes(“refresh_token”, “authorization_code”, “password”)
.accessTokenValiditySeconds(3600)
.scopes(“all”)
.and()
.withClient(“user-client”)
.secret(passwordEncoder.encode(“user-secret-8888”))
.authorizedGrantTypes(“refresh_token”, “authorization_code”, “password”)
.accessTokenValiditySeconds(3600)
.scopes(“all”);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients();
security.checkTokenAccess(“isAuthenticated()”);
security.tokenKeyAccess(“isAuthenticated()”);
}
}
有三个 configure 方法的重写。
AuthorizationServerEndpointsConfigurer参数的重写
endpoints.authenticationManager(authenticationManager)
.userDetailsService(kiteUserDetailsService)
.tokenStore(redisTokenStore);
复制代码
authenticationManage() 调用此方法才能支持 password 模式。
userDetailsService() 设置用户验证服务。
tokenStore() 指定 token 的存储方式。
redisTokenStore Bean 的定义如下:
@Configuration
public class RedisTokenStoreConfig {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public TokenStore redisTokenStore (){
return new RedisTokenStore(redisConnectionFactory);
}
}
ClientDetailsServiceConfigurer参数的重写,在这里定义各个端的约束条件。包括
ClientId、Client-Secret:这两个参数对应请求端定义的 cleint-id 和 client-secret
authorizedGrantTypes 可以包括如下几种设置中的一种或多种:
authorization_code:授权码类型。
implicit:隐式授权类型。
password:资源所有者(即用户)密码类型。
client_credentials:客户端凭据(客户端ID以及Key)类型。
refresh_token:通过以上授权获得的刷新令牌来获取新的令牌。
accessTokenValiditySeconds:token 的有效期
scopes:用来限制客户端访问的权限,在换取的 token 的时候会带上 scope 参数,只有在 scopes 定义内的,才可以正常换取 token。
上面代码中是使用 inMemory 方式存储的,将配置保存到内存中,相当于硬编码了。正式环境下的做法是持久化到数据库中,比如 mysql 中。
具体的做法如下:
create table oauth_client_details (
client_id VARCHAR(256) PRIMARY KEY,
resource_ids VARCHAR(256),
client_secret VARCHAR(256),
scope VARCHAR(256),
authorized_grant_types VARCHAR(256),
web_server_redirect_uri VARCHAR(256),
authorities VARCHAR(256),
access_token_validity INTEGER,
refresh_token_validity INTEGER,
additional_information VARCHAR(4096),
autoapprove VARCHAR(256)
);
INSERT INTO oauth_client_details
(client_id, client_secret, scope, authorized_grant_types,
web_server_redirect_uri, authorities, access_token_validity,
refresh_token_validity, additional_information, autoapprove)
VALUES
(‘user-client’, ‘$2a 10 10 10o2l5kA7z.Caekp72h5kU7uqdTDrlamLq.57M1F6ulJln9tRtOJufq’, ‘all’,
‘authorization_code,refresh_token,password’, null, null, 3600, 36000, null, true);
INSERT INTO oauth_client_details
(client_id, client_secret, scope, authorized_grant_types,
web_server_redirect_uri, authorities, access_token_validity,
refresh_token_validity, additional_information, autoapprove)
VALUES
(‘order-client’, ‘$2a 10 10 10GoIOhjqFKVyrabUNcie8d.ADX.qZSxpYbO6YK4L2gsNzlCIxEUDlW’, ‘all’,
‘authorization_code,refresh_token,password’, null, null, 3600, 36000, null, true);
注意: client_secret 字段不能直接是 secret 的原始值,需要经过加密。因为是用的 BCryptPasswordEncoder,所以最终插入的值应该是经过 BCryptPasswordEncoder.encode()之后的值。
spring:
datasource:
url: jdbc:mysql://localhost:3306/spring_cloud?characterEncoding=UTF-8&useSSL=false
username: root
password: password
hikari:
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
maximum-pool-size: 9
Spring Boot 2.0 之后默认使用 hikari 作为数据库连接池。如果使用其他连接池需要引入相关包,然后对应的增加配置。
@Autowired
private DataSource dataSource;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
JdbcClientDetailsServiceBuilder jcsb = clients.jdbc(dataSource);
jcsb.passwordEncoder(passwordEncoder);
}
还有一个重写的方法 public void configure(AuthorizationServerSecurityConfigurer security),这个方法限制客户端访问认证接口的权限。
security.allowFormAuthenticationForClients();
security.checkTokenAccess(“isAuthenticated()”);
security.tokenKeyAccess(“isAuthenticated()”);
第一行代码是允许客户端访问 OAuth2 授权接口,否则请求 token 会返回 401。
第二行和第三行分别是允许已授权用户访问 checkToken 接口和获取 token 接口。
完成之后,启动项目,如果你用的是 IDEA 会在下方的 Mapping 窗口中看到 oauth2 相关的 RESTful 接口。
主要有如下几个:
POST /oauth/authorize 授权码模式认证授权接口
GET/POST /oauth/token 获取 token 的接口
POST /oauth/check_token 检查 token 合法性接口
上面创建完成了认证服务端,下面开始创建一个客户端,对应到我们系统中的业务相关的微服务。我们假设这个微服务项目是管理用户相关数据的,所以叫做用户客户端。
1、引用相关的 maven 包
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-oauth2
org.springframework.boot
spring-boot-starter-data-redis
2、application.yml 配置文件
spring:
application:
name: client-user
redis:
database: 2
host: localhost
port: 32768
password: 1qaz@WSX
jedis:
pool:
max-active: 8
max-idle: 8
min-idle: 0
timeout: 100ms
server:
port: 6101
servlet:
context-path: /client-user
security:
oauth2:
client:
client-id: user-client
client-secret: user-secret-8888
user-authorization-uri: http://localhost:6001/oauth/authorize
access-token-uri: http://localhost:6001/oauth/token
resource:
id: user-client
user-info-uri: user-info
authorization:
check-token-access: http://localhost:6001/oauth/check_token
上面是常规配置信息以及 redis 配置,重点是下面的 security 的配置,这里的配置稍有不注意就会出现 401 或者其他问题。
client-id、client-secret 要和认证服务中的配置一致,如果是使用 inMemory 还是 jdbc 方式。
user-authorization-uri 是授权码认证方式需要的,下一篇文章再说。
access-token-uri 是密码模式需要用到的获取 token 的接口。
authorization.check-token-access 也是关键信息,当此服务端接收到来自客户端端的请求后,需要拿着请求中的 token 到认证服务端做 token 验证,就是请求的这个接口
3、资源配置文件
在 OAuth2 的概念里,所有的接口都被称为资源,接口的权限也就是资源的权限,所以 Spring Security OAuth2 中提供了关于资源的注解 @EnableResourceServer,和 @EnableWebSecurity的作用类似。
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Value(“${security.oauth2.client.client-id}”)
private String clientId;
@Value(“${security.oauth2.client.client-secret}”)
private String secret;
@Value(“${security.oauth2.authorization.check-token-access}”)
private String checkTokenEndpointUrl;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public TokenStore redisTokenStore (){
return new RedisTokenStore(redisConnectionFactory);
}
@Bean
public RemoteTokenServices tokenService() {
RemoteTokenServices tokenService = new RemoteTokenServices();
tokenService.setClientId(clientId);
tokenService.setClientSecret(secret);
tokenService.setCheckTokenEndpointUrl(checkTokenEndpointUrl);
return tokenService;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenServices(tokenService());
}
}
因为使用的是 redis 作为 token 的存储,所以需要特殊配置一下叫做 tokenService 的 Bean,通过这个 Bean 才能实现 token 的验证。
4、最后,添加一个 RESTful 接口
@Slf4j
@RestController
public class UserController {
@GetMapping(value = “get”)
//@PreAuthorize(“hasAuthority(‘ROLE_ADMIN’)”)
@PreAuthorize(“hasAnyRole(‘ROLE_ADMIN’)”)
public Object get(Authentication authentication){
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
为了这次面试,也收集了很多的面试题!
以下是部分面试题截图
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门即可获取!
tion){
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-MCjZSaWC-1712518660966)]
[外链图片转存中…(img-iopZ956b-1712518660967)]
[外链图片转存中…(img-4LlJeCBC-1712518660967)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
为了这次面试,也收集了很多的面试题!
以下是部分面试题截图
[外链图片转存中…(img-xY3NLUxc-1712518660968)]
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门即可获取!