社区群聊
添加小编微信进入java学习交流群,小编微信:372787553,备注进群
本文是基于数据库实现的,如果您想基于Redis实现,请移步到SpringCloud+OAuth2 统一权限验证
OAuth2简介
OAuth2.0是OAuth协议的延续版本,但不向后兼容OAuth 2.0即完全废止了OAuth1.0。 OAuth
2.0关注客户端开发者的简易性。要么通过组织在资源拥有者和HTTP服务商之间的被批准的交互动作代表用户,要么允许第三方应用代表用户获得访问的权限
关键名词
在详细讲解OAuth 2.0之前,需要了解几个专用名词。它们对读懂后面的讲解,尤其是几张图,至关重要。
(1) Third-party application:第三方应用程序,
(2)HTTP service:HTTP服务提供商,本文中简称"服务提供商",
(3)Resource Owner:资源所有者
(4)User Agent:用户代理,本文中就是指浏览器。
(5)Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器
(6)Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。
知道了上面这些名词,就不难理解,OAuth的作用就是让"客户端"安全可控地获取"用户"的授权,与"服务商提供商"进行互动。
以上介绍节选自上一篇文章SpringCloud+OAuth2 统一权限验证https://blog.csdn.net/weixin_38937840/article/details/90321037
这篇文章使用的是Redis存储token,本文我们将介绍使用JDBC的方式存储token
搭建项目
创建服务
javayh-server是统一认证服务器;
javayh-resource是资源服务器;
核心依赖
由于篇幅较长,这里我只列出了一部分,文章末尾会有源码地址
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
<!-- druid 官方 starter -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.connector.version}</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelpe.version}</version>
</dependency>
数据库脚本
由于SQL脚本所占的篇幅过长,我放在了另一个文档里,点击查看
初始化SQL https://blog.csdn.net/weixin_38937840/article/details/105382901
认证服务器配置
以上的准备工作都完成了,我们就可以配置认证服务器了;
配置统一认证处理
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration
extends AuthorizationServerConfigurerAdapter {
@Autowired(required = false)
private AuthenticationManager authenticationManager;
@Autowired
private DataSource dataSource;
@Autowired
@Qualifier("userDetailsServiceImpl")
private UserDetailsService userDetailsService;
/**
* 自定义获取客户端,为了支持自定义权限,
*/
@Bean
public ClientDetailsService clientDetails() {
JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(
dataSource);
jdbcClientDetailsService.setPasswordEncoder(new BCryptPasswordEncoder());
return jdbcClientDetailsService;
}
/**
* 令牌存放
* @return
*/
@Bean
public TokenStore tokenStore() {
// 基于 JDBC 实现,令牌保存到数据
return new JdbcTokenStore(dataSource);
}
/**
* 授权store
* @return
*/
@Bean
public ApprovalStore approvalStore() {
return new JdbcApprovalStore(dataSource);
}
/**
* 令牌信息拓展
* @return
*/
@Bean
public TokenEnhancer tokenEnhancer() {
return new BaseTokenEnhancer();
}
/**
* 授权码
* @return
*/
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(dataSource);
}
/**
* 配置客户端详细信息服务
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
clients.withClientDetails(clientDetails());
}
/**
* 令牌访问端点
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints
// 允许post提交
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
// 密码模式需要
.authenticationManager(authenticationManager)
.approvalStore(approvalStore()).userDetailsService(userDetailsService)
.tokenServices(createDefaultTokenServices())
.accessTokenConverter(SecurityHelper.buildAccessTokenConverter())
// 设置令牌存储在数据库
.tokenStore(tokenStore())
.authorizationCodeServices(authorizationCodeServices());
}
private DefaultTokenServices createDefaultTokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
// 令牌存储策略
tokenServices.setTokenStore(tokenStore());
tokenServices.setTokenEnhancer(tokenEnhancer());
// 是否产生刷新令牌
tokenServices.setSupportRefreshToken(true);
tokenServices.setReuseRefreshToken(true);
tokenServices.setClientDetailsService(clientDetails());
return tokenServices;
}
/**
* 令牌访问端点安全策略
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security)
throws Exception {
security
// 开启/oauth/check_token验证端口认证权限访问
.checkTokenAccess("isAuthenticated()")
// 开启表单认证,申请令牌
.allowFormAuthenticationForClients()
// 允许表单验证
.checkTokenAccess("permitAll()");
}
}
服务器安全配置
package com.javayh.oauth2.server.conf;
import com.javayh.oauth2.server.conf.service.UserDetailsServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.WebSecurity;
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;
/**
* <p>
* 授权回调
* </p>
* @version 1.0.0
* @author Dylan-haiji
* @since 2020/4/8
*/
@Slf4j
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Bean
public BCryptPasswordEncoder passwordEncoder() {
// 设置默认的加密方式
return new BCryptPasswordEncoder();
}
@Bean
@Override
public UserDetailsService userDetailsService() {
return new UserDetailsServiceImpl();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 使用自定义认证与授权
auth.userDetailsService(userDetailsService());
}
@Override
public void configure(WebSecurity web) throws Exception {
// 将 check_token 暴露出去,否则资源服务器访问时报 403 错误
web.ignoring().antMatchers("/oauth/check_token");
}
}
自定义权限配置
package com.javayh.oauth2.server.conf.service;
import com.google.common.collect.Lists;
import com.javayh.oauth.domain.TbPermission;
import com.javayh.oauth.domain.TbUser;
import com.javayh.oauth2.server.service.TbPermissionService;
import com.javayh.oauth2.server.service.TbUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* <p>
* 自定义用户认证与授权
* </p>
*
* @version 1.0.0
* @author Dylan-haiji
* @since 2020/4/5
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private TbUserService tbUserService;
@Autowired
private TbPermissionService tbPermissionService;
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
TbUser tbUser = tbUserService.getByUsername(username);
List<GrantedAuthority> grantedAuthorities = Lists.newArrayList();
if (tbUser != null) {
// 获取用户授权
List<TbPermission> tbPermissions = tbPermissionService
.selectByUserId(tbUser.getId());
// 声明用户授权
tbPermissions.forEach(tbPermission -> {
if (tbPermission != null && tbPermission.getEnname() != null) {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(
tbPermission.getEnname());
grantedAuthorities.add(grantedAuthority);
}
});
}
return new User(tbUser.getUsername(), tbUser.getPassword(), grantedAuthorities);
}
}
到这授权服务器配置完成,关于配置文件,请参考github配置文件 https://github.com/Dylan-haiji/javayh-platform/tree/master/javayh-sso/javayh-server/src/main/resources
资源服务器配置
资源服务器配置
package com.javayh.resource.conf;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.context.annotation.Configuration;
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.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
/**
* <p>
* 资源服务器配置
* </p>
* @version 1.0.0
* @author Dylan-haiji
* @since 2020/4/8
*/
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http
.antMatcher("/**").authorizeRequests();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and().authorizeRequests()
// 指定监控访问权限
.requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll().anyRequest()
.authenticated().and().exceptionHandling()
// 认证鉴权错误处理,为了统一异常处理。每个资源服务器都应该加上。
// .accessDeniedHandler(new AccessDeniedHandler())
// .authenticationEntryPoint(new WebException())
// 配置跨域
.and().cors().and().csrf().disable()
// 禁用httpBasic
.httpBasic().disable();
registry.anyRequest().authenticated();
}
}
配置文件
security:
oauth2:
client:
client-id: client
client-secret: secret
access-token-uri: http://localhost:9090/oauth/token
user-authorization-uri: http://localhost:9090/oauth/authorize
resource:
token-info-uri: http://localhost:9090/oauth/check_token
调用流程
获取token
- 启动服务服务获取code
访问:http://localhost:9090/oauth/authorize?client_id=client&response_type=code
- 初次需要登录,如下图
登录名:admin , 密码:123456
-
获取code
-
获取token
这里我们使用postman获取token
访问:http://client:secret@localhost:9090/oauth/token
如下图:
详细步骤:https://github.com/Dylan-haiji/javayh-platform/tree/master/javayh-sso/javayh-server
根据token获取资源
- 访问资源
http://localhost:9099/api/user/info
这时有两种方式访问
-
access_toke
-
Authorization
headers携带
详细步骤:https://github.com/Dylan-haiji/javayh-platform/tree/master/javayh-sso/javayh-resource
本问全部源码以放在github上,欢迎点赞,或者加入开发,也可加入社区交流群,添加小编微信:372787553
地址:https://github.com/Dylan-haiji/javayh-platform/tree/master/javayh-sso
本文的分享暂时就到这里,希望对您有所帮助
关注 Java有货领取更多资料
联系小编。微信:372787553,带您进群互相学习
左侧小编微信,右侧获取免费资料
- SpringCloud 自定义封装架构https://github.com/yanghaiji/javayh-platform
- Java 设计模式学习代码 https://github.com/yanghaiji/design-pattern
- SpringCloud学习代码: https://github.com/yanghaiji/javayh-cloud
- AlibabaCloud学习代码:https://github.com/yanghaiji/javayh-cloud-nacos
- SpringBoot+Mybatis 多数据源切换:https://github.com/yanghaiji/javayh-boot-data-soure
- Redis、Mongo、Rabbitmq、Kafka学习代码: https://github.com/yanghaiji/javayh-middleware
- SpringBoot+SpringSecurity实现自定义登录学习代码:https://github.com/yanghaiji/javayh-distribution