OAuth2とは何ですか?
それは、オープンスタンダードの認可できるように設計され、ユーザーができるように、サードパーティ製のアプリケーションをアクセスユーザーが変更するために、サーバー内の特定の民間の資源を、しかし、サードパーティのアプリケーションにサーバーにアカウントパスワードで提供されない場合があります
システムのリソースが保護されている場合、他のサードパーティアプリケーションがこれらの保護されたリソースにアクセスするためにOAuth2プロトコルを使用する必要があることを意味します。これは通常、他のWebサイトでQQを使用するなど、一部の許可されたログインシナリオで使用されます。 WeChatログインなど
OAuth2のいくつかの役割
ユーザー:サードパーティのアプリケーション(または独自のアプリケーション)を使用するユーザー。
クライアント:ユーザーが使用するサードパーティのアプリケーション。
認証(承認)サーバー:トークンの発行を担当するサービスと、保護されたリソースにアクセスするクライアントは、認証サーバーに情報を登録する必要があります。
リソースサーバー:保護されたAPIリソース。
写真を使用して、これら4つの役割の関係を説明できます。
Spring Security OAuth2を使用して、認証サーバーとリソースサーバーを構築します
1.認証サーバーを構築します
現在のプロジェクトの多くはマイクロサービスアーキテクチャプロジェクトです。APIリソースを提供するマイクロサービスごとに、それはリソースサーバーであり、クライアントが各マイクロサービスにアクセスするときの認証サーバーは認証サーバーです。訪問するトークンのみを取得します。
各マイクロサービスの依存関係を追加します。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
サービスの1つは、認証構成をセットアップするための認証サーバーとして使用されます。
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
/**
* 配置authenticationManager用于认证的过程
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager);
}
/**
* 重写此方法用于声明认证服务器能认证的客户端信息
* 相当于在认证服务器中注册哪些客户端(包括资源服务器)能访问
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("orderApp") //声明访问认证服务器的客户端
.secret(passwordEncoder.encode("123456")) //客户端访问认证服务器需要带上的密码
.scopes("read","write") //获取token包含的哪些权限
.accessTokenValiditySeconds(3600) //token过期时间
.resourceIds("order-service") //指明请求的资源服务器
.authorizedGrantTypes("password") //密码模式
.and()
//资源服务器拿到了客户端请求过来的token之后会请求认证服务器去判断此token是否正确或者过期
//所以此时的资源服务器对于认证服务器来说也充当了客户端的角色
.withClient("order-service")
.secret(passwordEncoder.encode("123456"))
.scopes("read")
.accessTokenValiditySeconds(3600)
.resourceIds("order-service")
.authorizedGrantTypes("password");
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("isAuthenticated()");
}
}
最初に@EnableAuthorizationServerアノテーションを追加して、これが認証サーバーであることを宣言します。次に、2番目に書き直されたconfigureメソッドでクライアント情報を設定します。これは、登録されたクライアントのみが認証サーバーにアクセスできるのと同等であり、クライアントはこれらを伝送します。情報とユーザーアカウントのパスワードおよびその他の情報は、認証サーバーへの要求を開始します。ここではパスワードモードになっているため(パスワードモードは通常、APIのセキュリティを保護するために使用されます)、もちろん、ユーザー名とパスワードを確認する必要があります。これらの確認プロセスでは、 AuthenticationManager(最初に書き直されたconfigureメソッド)、およびAuthenticationManagerの取得元も、セットアップする必要があります。SpringSecurityのユーザー情報の認証に関連する新しい構成クラスを作成します。
@Configuration
@EnableWebSecurity
public class OAuth2AuthWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
UserDetailServiceImpl:
@Component
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
//认证的过程,由AuthenticationManager去调,从数据库中查找用户信息
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return User.withUsername(username)
.password(passwordEncoder.encode("123456"))
.authorities("ROLE_ADMIN")
.build();
}
}
アプリケーションを起動し、postmanを使用してhttp:// localhost:9000 / auth / tokenにアクセスします
ユーザー名とパスワードはログインユーザー名とパスワードです。データベースに保存されているユーザー名とパスワードと一致しない場合、認証は失敗します。 、Grant_typeはパスワードであり、現在のモードがパスワードであり、スコープが要求されたアクセス許可であることを示します。
受信した応答。access_tokenは認証が成功した後に認証サーバーから返されるトークンであり、expires_inは有効期限が切れる残りの時間です。リソースサーバーをリクエストするたびにトークンを持参する必要があります。その後、リソースサーバーは認証サーバーにトークンが正しいかどうかを確認するようにリクエストします。このプロセスでは、リソースサーバーのリソースにアクセスできるかどうかは、リソースサーバーが信頼する認証サーバーによって異なります。
1.2クライアント情報とトークンをデータベースに保存します
この時点で、認証サーバーは完成していますが、上記のクライアントの情報とトークンはメモリに保存されています。これは明らかに実際の使用には適していないため、データベースに保存できます。
データベースに対応するテーブルを作成します。
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)
);
create table oauth_access_token (
token_id VARCHAR(256),
token BLOB,
authentication_id VARCHAR(256) PRIMARY KEY,
user_name VARCHAR(256),
client_id VARCHAR(256),
authentication BLOB,
refresh_token VARCHAR(256)
);
クライアント情報をoauth_client_detailsテーブルに追加します。
データベースの依存関係を追加します。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
認証サーバーの構成を変更します。
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private DataSource dataSource;
@Bean
public TokenStore tokenStore(){
return new JdbcTokenStore(dataSource);
}
/**
* 配置authenticationManager用于认证的过程
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
//设置tokenStore,生成token时会向数据库中保存
.tokenStore(tokenStore())
.authenticationManager(authenticationManager);
}
/**
* 重写此方法用于声明认证服务器能认证的客户端信息
* 相当于在认证服务器中注册哪些客户端(包括资源服务器)能访问
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("isAuthenticated()");
}
}
このとき、認証サーバーを再起動すると、クライアントがトークンリクエストを開始したときに生成されたトークンがデータベースに保存され、認証サーバーが再起動すると、同じクライアントが再度リクエストし、データベース内のトークンが判定されます。トークンの有効期限が切れているかどうか、そうでない場合は直接クライアントに返します。有効期限が切れていない場合は、トークンを再生成し、データベース内の古いトークンを更新します。
2.リソースサーバーを構築します
新しいmavenプロジェクトを作成し、OAuth2依存関係を追加して、リソースサーバーを構成します。
@Configuration
@EnableResourceServer //声明该服务是一个资源服务器
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
//声明该资源服务器的id,当请求过来是会首先判断token是否有访问该资源服务器的权限
resources.resourceId("order-service");
}
/**
* 设置访问权限需要重写该方法
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//访问post请求的接口必须要有write权限
.antMatchers(HttpMethod.POST).access("#oauth2.hasScope('write')")
//访问get请求的接口必须要有read权限
.antMatchers(HttpMethod.GET).access("#oauth2.hasScope('read')");
}
}
@Configuration
//资源服务器拿到token之后需要向认证服务器发出请求判断该token是否正确,开启SpringSecurity认证的过程
@EnableWebSecurity
public class OAuth2ResourceWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public ResourceServerTokenServices tokenServices() {
RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
//认证时资源服务器就相当于客户端,需要向认证服务器声明自己的信息是否匹配
remoteTokenServices.setClientId("order-service");
remoteTokenServices.setClientSecret("123456");
remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:9000/oauth/check_token");
return remoteTokenServices;
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
OAuth2AuthenticationManager oAuth2AuthenticationManager = new OAuth2AuthenticationManager();
oAuth2AuthenticationManager.setTokenServices(tokenServices());
return oAuth2AuthenticationManager;
}
}
2.1認証後にユーザー情報を取得する
リソースサーバーの一部のインターフェイスで認証ユーザー情報を使用する場合があり、SpringSecurity OAuth2はそれを取得するための対応するメソッドも提供します。たとえば、次のようにインターフェイスパラメータに@AuthenticationPricipalアノテーションを追加できます。
@PostMapping("/create")
public OrderInfo order(@RequestBody OrderInfo orderInfo, @AuthenticationPrincipal String username){
PriceInfo priceInfo = restTemplate.getForObject("http://localhost:9002/price/get", PriceInfo.class);
orderInfo.setPrice(priceInfo.getPrice());
System.out.println("order() username is ===================" + username);
return orderInfo;
}
トークンにはユーザー名が含まれているため、デフォルトではユーザー名のみを取得できます。ユーザーオブジェクト全体の情報を取得する場合は、UserDetailServiceを使用して、ユーザー名に基づいてデータベースにクエリを実行し、構成クラスを変更できます。
@Configuration
//资源服务器拿到token之后需要向认证服务器发出请求判断该token是否正确,开启SpringSecurity认证的过程
@EnableWebSecurity
public class OAuth2ResourceWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Bean
public ResourceServerTokenServices tokenServices() {
RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
//认证时资源服务器就相当于客户端,需要向认证服务器声明自己的信息是否匹配
remoteTokenServices.setClientId("order-service");
remoteTokenServices.setClientSecret("123456");
remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:9000/oauth/check_token");
remoteTokenServices.setAccessTokenConverter(getAccessTokenConverter());
return remoteTokenServices;
}
//设置了AccessTokenConverter就能在认证之后通过token里面的username去调用UserDetailsService去数据库查找出完整的用户信息了
private AccessTokenConverter getAccessTokenConverter() {
DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
DefaultUserAuthenticationConverter userTokenConverter = new DefaultUserAuthenticationConverter();
userTokenConverter.setUserDetailsService(userDetailsService);
accessTokenConverter.setUserTokenConverter(userTokenConverter);
return accessTokenConverter;
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
OAuth2AuthenticationManager oAuth2AuthenticationManager = new OAuth2AuthenticationManager();
oAuth2AuthenticationManager.setTokenServices(tokenServices());
return oAuth2AuthenticationManager;
}
}
UserDetailServiceを追加した後、リソースサーバーは内部でloadUserByUsernameメソッドを呼び出し、認証が成功した後に認証サーバーにトークンを送信した後、ユーザーオブジェクトを返します。ユーザーオブジェクトは、次の時点でインターフェイスメソッドパラメーターによって取得できます。
@PostMapping("/create")
public OrderInfo order(@RequestBody OrderInfo orderInfo, @AuthenticationPrincipal User user){
PriceInfo priceInfo = restTemplate.getForObject("http://localhost:9002/price/get", PriceInfo.class);
orderInfo.setPrice(priceInfo.getPrice());
System.out.println("order() user is ===================" + user);
return orderInfo;
}
このオブジェクトに特定の属性が必要な場合は、注釈にel式を使用できます。
@PostMapping("/create")
public OrderInfo order(@RequestBody OrderInfo orderInfo, @AuthenticationPrincipal(expression = "#this.id") Long id){
PriceInfo priceInfo = restTemplate.getForObject("http://localhost:9002/price/get", PriceInfo.class);
orderInfo.setPrice(priceInfo.getPrice());
System.out.println("order() useId is ===================" + id);
return orderInfo;
}
それ以来、SpringSecurityOAuth2を使用して構築された単純な認証サーバーとリソースサーバーが完成しました。