一、结构基础
基础框架:Spring Boot + Spring-Security-OAuth2
存储介质:Mysql
持久化方式:Spring-data-jpa
测试工具:Postman
框架版本:SpringBoot:2.1.6 SpringCloud:Greenwich.SR1
大局观:
1、OAuth2服务器分为两部分组成:认证授权服务器和资源服务器。闻名知意,不解释。本文只讲认证授权服务器的搭建,资源服务器部分后续。
2、认证授权服务器分为两大步骤,一是认证,二是授权。而认证则主要由Spring-Security负责,而授权则有Oauth2负责。
二、application配置文件
1 | #关于Thymeleaf的配置 |
2 | spring.thymeleaf.mode=HTML5 |
3 | spring.thymeleaf.encoding=UTF-8 |
4 | spring.thymeleaf.cache=false |
5 | #关于Mariadb数据库的链接属性 |
6 | spring.datasource.url=jdbc:mariadb://localhost:3306/oauth2?useUnicode=true&characterEncoding=utf-8&useSSL=false |
7 | spring.datasource.username=root |
8 | spring.datasource.password=123456 |
9 | #允许bean的覆盖 |
10 | spring.main.allow-bean-definition-overriding=true |
三、添加POM文件
添加POM文件之前需要添加SpringCloud版本
SpringSecurity Oauth2依赖
1 | <dependency> |
2 | <groupId>org.springframework.boot</groupId> |
3 | <artifactId>spring-boot-starter-oauth2-client</artifactId> |
4 | </dependency> |
5 | <dependency> |
6 | <groupId>org.springframework.boot</groupId> |
7 | <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> |
8 | </dependency> |
9 | <dependency> |
10 | <groupId>org.springframework.cloud</groupId> |
11 | <artifactId>spring-cloud-starter-oauth2</artifactId> |
12 | </dependency> |
SpringSecurity依赖
1 | <dependency> |
2 | <groupId>org.springframework.cloud</groupId> |
3 | <artifactId>spring-cloud-starter-security</artifactId> |
4 | </dependency> |
5 | <dependency> |
6 | <groupId>org.springframework.boot</groupId> |
7 | <artifactId>spring-boot-starter-security</artifactId> |
8 | </dependency> |
9 | <dependency> |
10 | <groupId>org.springframework.security.oauth.boot</groupId> |
11 | <artifactId>spring-security-oauth2-autoconfigure</artifactId> |
12 | </dependency> |
四、编写授权配置
(一)新建AuthAuthorizeConfig这个文件
1 | 4j |
2 | |
3 | |
4 | public class AuthAuthorizeConfig extends AuthorizationServerConfigurerAdapter { |
5 | |
6 | |
7 | private DataSource dataSource; |
8 | |
9 | |
10 | private AuthenticationManager authenticationManager; |
11 | |
12 | |
13 | private MyUserDetailsService userDetailsService; |
14 | |
15 | |
16 | public BCryptPasswordEncoder encoder() { |
17 | return new BCryptPasswordEncoder(); |
18 | } |
19 | |
20 | |
21 | public TokenStore tokenStore() { |
22 | return new JwtTokenStore(accessTokenConverter()); |
23 | } |
24 | |
25 | |
26 | public JwtAccessTokenConverter accessTokenConverter() { |
27 | log.info("【accessTokenConverter 创建。。】"); |
28 | JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); |
29 | converter.setSigningKey("123"); |
30 | converter.setAccessTokenConverter(new JwtConverter()); |
31 | return converter; |
32 | } |
33 | |
34 | public static class JwtConverter extends DefaultAccessTokenConverter implements JwtAccessTokenConverterConfigurer { |
35 | |
36 | |
37 | public void configure(JwtAccessTokenConverter converter) { |
38 | converter.setAccessTokenConverter(this); |
39 | } |
40 | |
41 | |
42 | public OAuth2Authentication extractAuthentication(Map<String, ?> map) { |
43 | log.info("=====================" + map.toString()); |
44 | OAuth2Authentication auth = super.extractAuthentication(map); |
45 | auth.setDetails(map); //this will get spring to copy JWT content into Authentication |
46 | return auth; |
47 | } |
48 | } |
49 | |
50 | |
51 | public void configure(ClientDetailsServiceConfigurer clients) throws Exception { |
52 | clients.jdbc(dataSource); |
53 | } |
54 | |
55 | |
56 | public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { |
57 | endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager) |
58 | .userDetailsService(userDetailsService) |
59 | .accessTokenConverter(accessTokenConverter()) |
60 | .exceptionTranslator(loggingExceptionTranslator()); |
61 | } |
62 | |
63 | |
64 | public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { |
65 | log.info("开始OAuth2Config的AuthorizationServerSecurityConfigurer"); |
66 | security.allowFormAuthenticationForClients() |
67 | .passwordEncoder(encoder()) |
68 | .tokenKeyAccess("permitAll()") |
69 | .checkTokenAccess("isAuthenticated()"); |
70 | } |
71 | |
72 | /** |
73 | * 自定义一个异常响应转换器,显示异常详情 |
74 | * @return |
75 | */ |
76 | |
77 | public WebResponseExceptionTranslator<OAuth2Exception> loggingExceptionTranslator() { |
78 | return new DefaultWebResponseExceptionTranslator() { |
79 | |
80 | public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception { |
81 | e.printStackTrace(); |
82 | // Carry on handling the exception |
83 | ResponseEntity<OAuth2Exception> responseEntity = super.translate(e); |
84 | HttpHeaders headers = new HttpHeaders(); |
85 | headers.setAll(responseEntity.getHeaders().toSingleValueMap()); |
86 | OAuth2Exception excBody = responseEntity.getBody(); |
87 | return new ResponseEntity<>(excBody, headers, responseEntity.getStatusCode()); |
88 | } |
89 | }; |
90 | } |
91 | } |
详解:
@Slf4j: 日志注解
@Configuration: 用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。
@EnableAuthorizationServer:开启授权服务
代码中有三个重要的配置类,也是三个@Override
(1)public void configure(ClientDetailsServiceConfigurer clients)
配置OAuth2的客户端相关信息,这个我们从数据库中的oauth_client_details这张表中读取,所以需要配置DataSource数据源
(2)public void configure(AuthorizationServerEndpointsConfigurer endpoints)
配置AuthorizationServerEndpointsConfigurer众多相关类,包括配置身份认证器,配置认证方式,TokenStore,TokenGranter,OAuth2RequestFactory
(3) public void configure(AuthorizationServerSecurityConfigurer security)
配置AuthorizationServer安全认证的相关信息,创建ClientCredentialsTokenEndpointFilter核心过滤器
ps:在注入authenticationManager这个类是,可能注入不进去,请查看异常合集
(二)编写Security安全文件
新建WebSecurityConfig文件
1 | 4j |
2 | |
3 | public class WebSecurityConfig extends WebSecurityConfigurerAdapter { |
4 | |
5 | |
6 | private MyUserDetailsService userService; |
7 | |
8 | |
9 | public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { |
10 | //校验用户 |
11 | auth.userDetailsService(userService); |
12 | } |
13 | |
14 | |
15 | protected void configure(HttpSecurity http) throws Exception { |
16 | log.info("开始HTTP授权服务器的配置"); |
17 | http.authorizeRequests() |
18 | .antMatchers("/login", "/login/**", "/oauth/**", "/token/**", "/error", "/401").permitAll() |
19 | .antMatchers(HttpMethod.OPTIONS).permitAll() |
20 | .anyRequest().authenticated() |
21 | .and() |
22 | .formLogin().loginPage("/login").failureUrl("/login-error").permitAll() |
23 | .and() |
24 | .logout().logoutSuccessUrl("/").and().csrf().disable(); |
25 | } |
26 | |
27 | /** |
28 | * 需要重新该方法,并且该方法是公开的(publiv) |
29 | * |
30 | * @return |
31 | * @throws Exception |
32 | */ |
33 | |
34 | |
35 | public AuthenticationManager authenticationManagerBean() throws Exception { |
36 | return super.authenticationManagerBean(); |
37 | } |
38 | |
39 | |
40 | public void configure(WebSecurity web) throws Exception { |
41 | //log.info("开始WebSecurityConfig的WebSecurity"); |
42 | web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**"); |
43 | web.ignoring().antMatchers("/actuator/health", "/favicon.ico", "/css/**", "/js/**","/images/**", "/fonts/**","/dist/**"); |
44 | } |
45 | } |
详解:
@Slf4j: 日志注解
@Configuration: 用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。
(1)protected void configure(HttpSecurity http)
主要配置安全文件,比如那个路径是不需要进行授权的,
1 | http.authorizeRequests() |
2 | .antMatchers("/login", "/login/**", "/oauth/**", "/token/**", "/error", "/401").permitAll() |
3 | .anyRequest().authenticated() |
这段话的意思是,http.授权请求().路径匹配器(“各个路径”….).允许不进行授权().其他请求().都要授权()
1 | .formLogin().loginPage("/login").failureUrl("/login-error").permitAll() |
这段话的意思是,.进行form表单登陆().登陆的页面是(“/login这个路劲”).登陆失败(“跳到这个路径”).允许登录路径不授权()
1 | .logout().logoutSuccessUrl("/").and().csrf().disable(); |
这段话的意思是,.登出().登出成功的URL(“路径”).并且().跨站请求().关闭()
(2)public void configureGlobal(AuthenticationManagerBuilder auth)
注入校验用户登陆是否成功的处理器
(三)编写MyUserDetailsService,用于处理用户信息
1 | |
2 | 4j |
3 | public class MyUserDetailsService implements UserDetailsService { |
4 | |
5 | |
6 | private UserRepository userRepository; |
7 | |
8 | |
9 | private RoleRepository roleRepository; |
10 | |
11 | |
12 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { |
13 | // 查询数据库 |
14 | Optional<User> userOptional = userRepository.findByUsername(username); |
15 | User user = null; |
16 | if (userOptional.isPresent()) { |
17 | user = userOptional.get(); |
18 | List<Role> roleList = roleRepository.getRoleByUserid(user.getId()); |
19 | /*user.setAuthorities(roleList); |
20 | return user;*/ |
21 | List<SimpleGrantedAuthority> authorities = getAuthorities(roleList); |
22 | return new org.springframework.security.core.userdetails.User(username, user.getPassword(), authorities); |
23 | } else { |
24 | log.error("用户不存在"); |
25 | throw new UsernameNotFoundException(String.format("User %s does not exist!", username)); |
26 | } |
27 | } |
28 | |
29 | |
30 | private List<SimpleGrantedAuthority> getAuthorities(List<Role> roles) { |
31 | List<SimpleGrantedAuthority> authList = new ArrayList<>(); |
32 | if (!CollectionUtils.isEmpty(roles)) { |
33 | for (Role role : roles) { |
34 | authList.add(new SimpleGrantedAuthority(role.getName())); |
35 | } |
36 | } |
37 | return authList; |
38 | } |
39 | } |
这个类主要判断用户是否登陆正确,判断用户名和密码是否匹配
五、测试
启动服务,开启postman
成功页面

编写,请求为POST请求,路径为localhost:8080/auth/oauth/token,
点击下方的选项卡中的Authorization,左边Type,选择Base Auth后右边填写oauth_client_details这张表下的client_id代表username,client_secrt代表password,
编写完上面后,点击Body,选择form-data
属性有
1 | password:你要登陆的用户 |
2 | grant_type:password |
3 | scope:所给权限(write,read,all) |
4 | client_id:client_id |
5 | client_secret:client_secrt |
6 | username:你要登陆的用户 |
示例:

点击请求后,出现Token后,代表配置成功

六、数据库解析
数据库文件:
1 | CREATE TABLE `oauth_client_details` ( |
2 | `client_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, |
3 | `resource_ids` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, |
4 | `client_secret` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, |
5 | `scope` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, |
6 | `authorized_grant_types` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, |
7 | `web_server_redirect_uri` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, |
8 | `authorities` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, |
9 | `access_token_validity` int(11) NULL DEFAULT NULL, |
10 | `refresh_token_validity` int(11) NULL DEFAULT NULL, |
11 | `additional_information` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, |
12 | `autoapprove` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, |
13 | PRIMARY KEY (`client_id`) USING BTREE |
14 | ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; |
字段解析:
client_id:主键,必须唯一,不能为空.
用于唯一标识每一个客户端(client); 在注册时必须填写(也可由服务端自动生成).
对于不同的grant_type,该字段都是必须的. 在实际应用中的另一个名称叫appKey,与client_id是同一个概念.
resource_ids:客户端所能访问的资源id集合,多个资源时用逗号(,)分隔,如: “unity-resource,mobile-resource”.
该字段的值必须来源于与security.xml中标签‹oauth2:resource-server的属性resource-id值一致. 在security.xml配置有几个‹oauth2:resource-server标签, 则该字段可以使用几个该值.
在实际应用中, 我们一般将资源进行分类,并分别配置对应的‹oauth2:resource-server,如订单资源配置一个‹oauth2:resource-server, 用户资源又配置一个‹oauth2:resource-server. 当注册客户端时,根据实际需要可选择资源id,也可根据不同的注册流程,赋予对应的资源id.
client_secret:用于指定客户端(client)的访问密匙; 在注册时必须填写(也可由服务端自动生成).
对于不同的grant_type,该字段都是必须的. 在实际应用中的另一个名称叫appSecret,与client_secret是同一个概念,使用BCryptPasswordEncoder加密的密文
scope:指定客户端申请的权限范围,可选值包括read,write,trust;若有多个权限范围用逗号(,)分隔,如: “read,write”.
authorized_grant_types:指定客户端支持的grant_type,可选值包括authorization_code,password,refresh_token,implicit,client_credentials, 若支持多个grant_type用逗号(,)分隔,如: “authorization_code,password”.在实际应用中,当注册时,该字段是一般由服务器端指定的,而不是由申请者去选择的,最常用的grant_type组合有: “authorization_code,refresh_token”(针对通过浏览器访问的客户端); “password,refresh_token”(针对移动设备的客户端).implicit与client_credentials在实际中很少使用.
web_server_redirect_uri:客户端的重定向URI,可为空, 当grant_type为authorization_code或implicit
时, 在Oauth的流程中会使用并检查与注册时填写的redirect_uri是否一致. 下面分别说明:
当grant_type=
authorization_code时, 第一步从 spring-oauth-server获取 'code'时客户端发起请求时必须有redirect_uri参数, 该参数的值必须与web_server_redirect_uri的值一致. 第二步用 'code' 换取 'access_token'时客户也必须传递相同的redirect_uri.
在实际应用中, web_server_redirect_uri在注册时是必须填写的, 一般用来处理服务器返回的code, 验证state是否合法与通过code去换取access_token值.
在spring-oauth-client项目中, 可具体参考AuthorizationCodeController.java中的authorizationCodeCallback方法.当grant_type=implicit时通过redirect_uri的hash值来传递access_token值.如:
1http://localhost:7777/spring-oauth-client/implicit#access_token=dc891f4a-ac88-4ba6-8224-a2497e013865&token_type=bearer&expires_in=43199然后客户端通过JS等从hash值中取到access_token值.
authorities:指定客户端所拥有的Spring Security的权限值,可选, 若有多个权限值,用逗号(,)分隔, 如: “ROLE_UNITY,ROLE_USER”.对于是否要设置该字段的值,要根据不同的grant_type来判断, 若客户端在Oauth流程中需要用户的用户名(username)与密码(password)的(authorization_code,password),则该字段可以不需要设置值,因为服务端将根据用户在服务端所拥有的权限来判断是否有权限访问对应的API.
但如果客户端在Oauth流程中不需要用户信息的(implicit,client_credentials),则该字段必须要设置对应的权限值, 因为服务端将根据该字段值的权限来判断是否有权限访问对应的API.
access_token_validity:设定客户端的access_token的有效时间值(单位:秒),可选, 若不设定值则使用默认的有效时间值(60 * 60 * 12, 12小时).在服务端获取的access_token JSON数据中的expires_in字段的值即为当前access_token的有效时间值.在项目中, 可具体参考DefaultTokenServices.java中属性accessTokenValiditySeconds.在实际应用中, 该值一般是由服务端处理的, 不需要客户端自定义.
refresh_token_validity: 设定客户端的refresh_token的有效时间值(单位:秒),可选, 若不设定值则使用默认的有效时间值(60 * 60 * 24 * 30, 30天).若客户端的grant_type不包括refresh_token,则不用关心该字段 在项目中, 可具体参考DefaultTokenServices.java中属性refreshTokenValiditySeconds.在实际应用中, 该值一般是由服务端处理的, 不需要客户端自定义.
additional_information:这是一个预留的字段,在Oauth的流程中没有实际的使用,可选,但若设置值,必须是JSON格式的数据,如:
1 | {"country":"CN","country_code":"086"} |
按照spring-security-oauth项目中对该字段的描述Additional information for this client, not need by the vanilla OAuth protocol but might be useful, for example,for storing descriptive information.
(详见ClientDetails.java的getAdditionalInformation()方法的注释)在实际应用中, 可以用该字段来存储关于客户端的一些其他信息,如客户端的国家,地区,注册时的IP地址等等.
autoapprove:设置用户是否自动Approval操作, 默认值为 ‘false’, 可选值包括 ‘true’,’false’, ‘read’,’write’.
该字段只适用于grant_type=”authorization_code”的情况,当用户登录成功后,若该值为’true’或支持的scope值,则会跳过用户Approve的页面, 直接授权.该字段与 trusted 有类似的功能, 是 spring-security-oauth2 的 2.0 版本后添加的新属性.
详细信息参考网址:http://andaily.com/spring-oauth-server/db_table_description.html