(1)创建is-server-auth 认证服务器工程
(2)pom.xml 依赖
以下是使用 OAuth2 的主要依赖配置
org.springframework.cloud
spring-cloud-starter-security
2.2.4.RELEASE
org.springframework.cloud
spring-cloud-starter-oauth2
2.2.4.RELEASE
org.springframework.boot
spring-boot-dependencies
2.3.4.RELEASE
pom
import
org.springframework.cloud
spring-cloud-dependencies
Greenwich.SR2
pom
import
我们需要新建一个认证服务器配置类 OAuth2AuthServerConfig ,继承 AuthorizationServerConfigurerAdapter ,AuthorizationServerConfigurerAdapter 是认证服务器适配器,我们看一下的源码:
public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer {
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
}
}
里面有三个方法,这三个方法,正对应上图中箭头所指的三个问题,我们需要重写这三个方法,实现自己的配置。
(1)配置Client信息
从图中可以看出,认证服务器要配置两个Client,一个是【客户端应用】,他需要来认证服务器申请令牌,一个是 【订单服务】,他要来认证服务器验令牌。
重写AuthorizationServerConfigurerAdapter 的 configure(ClientDetailsServiceConfigurer clients) throws Exception 方法
package com.imooc.security.server.auth;
import jdk.nashorn.internal.ir.annotations.Reference;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
/**
* @ClassName OAuth2AuthServerConfig
* @Description TODO 认证服务器
* @Author wushaopei
* @Date 2021/5/2 21:44
* @Version 1.0
*/
@Configuration //这是一个配置类
@EnableAuthorizationServer // 当前应用是一个认证服务器
public class OAuth2AuthServerConfig extends AuthorizationServerConfigurerAdapter {
//Spring 对密码加密的封装,自己配置下
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
/***
* @Description 配置客户端应用的信息,让认证服务器知道有哪些客户端应用来申请令牌。
* @param clients 客户端的详情服务的配置
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory() //添加客户端应用,配置在内存里,后面修改为数据库里
.withClient("orderApp")// 指定client的id,应用的用户名,这里添加的是客户端应用
.secret(passwordEncoder.encode("123456")) // 应用的密码
.scopes("read", "write") // 应用的权限
.accessTokenValiditySeconds(3600) // 令牌的有效期,单位为秒s
.resourceIds("order-server") // 资源服务器的id。指:我发给orderApp的token可以访问哪些资源服务器
.authorizedGrantTypes("password") // 授权方式,指可以用哪种方式去实现
.and()
.withClient("orderService")// 指定client的id,应用的用户名,这里添加的是订单服务。微服务中,订单服务应该具备访问其他服务的权利,同样需要获取令牌
.secret(passwordEncoder.encode("12345")) // 应用的密码
.scopes("read") // 应用的权限
.accessTokenValiditySeconds(3600) // 令牌的有效期,单位为秒s
.resourceIds("order-server") // 资源服务器的id。指:我发给orderApp的token可以访问哪些资源服务器
.authorizedGrantTypes("password"); // 授权方式,指可以用哪种方式去实现
}
}
(2)配置用户 信息
告诉认证服务器,有哪些用户可以来访问认证服务器
重写AuthorizationServerConfigurerAdapter 的 configure(AuthorizationServerEndpointsConfigurer endpoints) 方法
/**
* @Description TODO 配置用戶信息
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 传给他一个authenticationManager用来校验传过来的用户信息是不是合法的,注进来一个,自己实现
endpoints.authenticationManager(authenticationManager);
}
/**
* @Description TODO 配置资源服务器过来验token的规则
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
/**
* 过来验令牌有效性的请求,不是谁都能验的,必须要是经过身份认证的。
* 所谓身份认证就是,必须携带clientId,clientSecret,否则随便一请求过来验token是不验的
*/
security.checkTokenAccess("isAuthenticated()");
}
上边的 加密解密类 PasswordEncoder 和 配置用户信息的 AuthenticationManager 还没有实例的来源,下边配置这俩类。
(3)新建配置类 OAuth2WebSecurityConfig 继承 WebSecurityConfigurerAdapter
package com.imooc.security.server.auth;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @ClassName OAuth2WebSecurityConfig
* @Description TODO 用于声明客户端用户的信息
* @Author wushaopei
* @Date 2021/5/3 10:29
* @Version 1.0
*/
@Configuration
@EnableWebSecurity
public class OAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
public PasswordEncoder passwordEncoder;
/**
* @Description TODO AuthenticationManagerBuilder 是用来构建 AuthenticationManager(处理登录操作)的
* TODO 需要两个东西:userDetailsService 、passwordEncoder
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
/**
* @Description TODO 把AuthenticationManager暴露为bean
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
passwordEncoder 本应该配置在上述类里,但是配置后报错循环依赖,暂时将其写在启动类里,不报错
(4)UserDetailsService的实现
分析:UserDetailsService接口,只有一个方法,返回UserDetails 接口:
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
loadUserByUsername,这里不用比对密码,比对密码是在AuthenticationManager里做的。UserDetails接口如下,提供了一些见名知意的方法,我们需要自定义自己的UserDetails实现类,比如你的User类实现这个接口 :
public interface UserDetails extends Serializable {
// ~ Methods
// ========================================================================================================
Collection extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
自定义UserDetailsService 实现类:
/**
* @ClassName UserDetailsServiceImpl
* @Description TODO
* @Author wushaopei
* @Date 2021/5/3 10:44
* @Version 1.0
*/
@Component
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return User.withUsername(username)
.password(passwordEncoder.encode("123456"))
.authorities("ROLE_ADMIN")
.build();
}
}
AuthenticationManager 接口 也只有一个方法,实现类一般不需要自己实现。入参 是一个 Authentication 接口的实现,其中封装了认证的信息,不同的认证发的信息不一样,如用户名/密码 登录需要用户名密码,OAuth2则需要appId,appSecret,redirectURI等,不同的认证方式传过来的实现不同, authenticate 方法验证完了后将其中的信息更新调,返回。
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
所有的准备工作都做好了,下面启动应用,来申请一个OAuth2令牌
postman请求http://localhost:9090/oauth/token ,HttpBasic传入客户端id和客户端密码。
用图片生动解释了 OAuth2 中的角色和Spring接口 AuthorizationServerConfigurerAdapter 三个方法的关系,方便记忆
实现了OAuth2的密码模式,来申请token
存在问题:passwordEncoder 如果放在了 OAuth2WebSecurityConfig配置类里面,就会报循环依赖错误,有待解决