一、OAuth(开放授权)是一个开放标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容,OAuth2.0是OAuth协议的延续版本,但不向后兼容OAuth 1.0即完全废止了OAuth1.0。
二、认证和授权过程。
1)主要包含3中角色:
(1)服务提供方:Authorization Server
(2)资源拥有者:Resource Server
(3)客户端:Client
2)授权过程
(1)用户打开授权页面,询问用户授权
(2)用户同意授权
(3)客户端向授权服务器进行授权申请
(4)授权服务器进行认证,认证通过后,返回令牌(包含用户信息)
(5)客户端得到令牌后,通过携带令牌访问资源服务器资源
(6)资源服务器向授权/认证服务确认后,方可向客户端开发资源
三、授权服务器(Authorization Server),为了更加接近生产环境,我这里采用的都是数据库或者自定义配置,请注意注释部分。
1、目录结构:
2、依赖部分
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
3、启动项
package com.cetc; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient public class AuthServerApplication { public static void main(String[] args) { SpringApplication.run(AuthServerApplication.class, args); } }
4、基础配置application.yaml
server: port: 8694 servlet: context-path: /auth session: cookie: name: SESSION_AUTH_SERVER #因为我这里都是本地,所以需要修改会话名称 spring: application: name: auth-server thymeleaf: encoding: UTF-8 mode: HTML5 cache: false datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1/auth-server?useUnicode=true&characterEncoding=utf-8 username: root password: root type: com.zaxxer.hikari.HikariDataSource jpa: show-sql: true hibernate: ddl-auto: update naming: physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy database: mysql database-platform: org.hibernate.dialect.MySQL5InnoDBDialect eureka: client: service-url: defaultZone: http://127.0.0.1:8670/eureka/ # 实际开发中建议使用域名的方式
5、下面我们重点来看配置部分(为了节约篇幅,我这里就省略了AuthDetailsService、DataSourceConfiguration、PasswordEncoderConfiguration具体的可以参考:Spring-Boot之Security安全管理-10)
(1)security配置
package com.cetc.config; 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.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration @EnableWebSecurity //开启注解的使用 @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { //认证过程 @Autowired private AuthDetailsService authDetailsService; //加密方式 @Autowired private PasswordEncoder passwordEncoder; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(authDetailsService).passwordEncoder(passwordEncoder); } //基本没有什么太大的变化 @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .exceptionHandling() .and() .authorizeRequests() .antMatchers("/login.html").permitAll() .anyRequest().authenticated() .and() .formLogin() //注意我这里使用的是自定义的登录页面 .loginPage("/login.html") .loginProcessingUrl("/login") .and() .logout() .deleteCookies("SESSION_AUTH_SERVER"); } //注意加入bean主要是在authentication server的配置需要使用 @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
(2)Authorization配置
package com.cetc.config; import com.zaxxer.hikari.HikariDataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; 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; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; @Configuration @EnableAuthorizationServer public class AuthServerConfiguration extends AuthorizationServerConfigurerAdapter{ @Autowired private AuthDetailsService authDetailsService; @Autowired private AuthenticationManager authenticationManager; @Autowired private ClientDetailsService clientDetailsService; @Autowired private TokenStore tokenStore; @Bean public ClientDetailsService clientDetailsService(HikariDataSource dataSource) { //使用数据库的配置方式 return new JdbcClientDetailsService(dataSource); } @Bean public TokenStore tokenStore(HikariDataSource dataSource) { //token也使用数据的方式,后面会将JWT的使用方式 return new JdbcTokenStore(dataSource); } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security //token获取方式 .tokenKeyAccess("permitAll()") //检测加入权限 .checkTokenAccess("isAuthenticated()") //允许表单认证 .allowFormAuthenticationForClients(); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { //这里就是具体的授权管理过程了 clients.withClientDetails(clientDetailsService); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints //这里使用的认证方式为security配置方式 .authenticationManager(authenticationManager) //提供get和post的认证方式 .allowedTokenEndpointRequestMethods(HttpMethod.POST, HttpMethod.GET) //这里一定要配置userDetailsService,不然刷新token会出错,refresh_token .userDetailsService(authDetailsService) .tokenStore(tokenStore) //自定义认证页面 .pathMapping("/oauth/confirm_access", "/oauth/confirm_access"); } }
其一:认证过程因为使用的是数据库,那么需要数据库的表格支持,这里也提供了为schema.sql,官网:https://github.com/spring-projects/spring-security-oauth/blob/2.0.x/spring-security-oauth2/src/test/resources/schema.sql
说明:由于官方的脚本适用于HSQL,所以我MYSQL这里脚本进行了修改,主要是主键改成255的长度,LONGVARBINARY改为BLOB
schema.sql:
-- used in tests that use HSQL create table oauth_client_details ( client_id VARCHAR(255) 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_client_token ( token_id VARCHAR(256), token BLOB, authentication_id VARCHAR(255) PRIMARY KEY, user_name VARCHAR(256), client_id VARCHAR(256) ); create table oauth_access_token ( token_id VARCHAR(256), token BLOB, authentication_id VARCHAR(255) PRIMARY KEY, user_name VARCHAR(256), client_id VARCHAR(256), authentication BLOB, refresh_token VARCHAR(256) ); create table oauth_refresh_token ( token_id VARCHAR(256), token BLOB, authentication BLOB ); create table oauth_code ( code VARCHAR(256), authentication BLOB ); create table oauth_approvals ( userId VARCHAR(256), clientId VARCHAR(256), scope VARCHAR(256), status VARCHAR(10), expiresAt TIMESTAMP, lastModifiedAt TIMESTAMP ); -- customized oauth_client_details table create table ClientDetails ( appId VARCHAR(255) PRIMARY KEY, resourceIds VARCHAR(256), appSecret VARCHAR(256), scope VARCHAR(256), grantTypes VARCHAR(256), redirectUrl VARCHAR(256), authorities VARCHAR(256), access_token_validity INTEGER, refresh_token_validity INTEGER, additionalInformation VARCHAR(4096), autoApproveScopes VARCHAR(256) );