概念
客户应用: 通常是一个web或者手机,他需要访问用户的受保护资源
资源服务器::是一个web站点或者web service API,用户的受保护数据保存在这个地方
授权服务器: 在客户应用成功认证并获得授权之后,想客户应用颁发访问令牌Access Token
资源拥有者:资源的拥有人,也就是用户。他可以想要分享给第三方应用的一些操作
一、引入核心包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
二、使用mysql数据库,引入jar包
<!--spring jpa-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
三、建表
/*
Navicat MySQL Data Transfer
Source Server : mysql本地
Source Server Version : 50096
Source Host : localhost:3306
Source Database : test
Target Server Type : MYSQL
Target Server Version : 50096
File Encoding : 65001
Date: 2021-05-30 18:37:06
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for authority
-- ----------------------------
DROP TABLE IF EXISTS `authority`;
CREATE TABLE `authority` (
`id` bigint(11) NOT NULL COMMENT '权限id',
`authority` varchar(255) default NULL COMMENT '权限',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for clientdetails
-- ----------------------------
DROP TABLE IF EXISTS `clientdetails`;
CREATE TABLE `clientdetails` (
`appId` varchar(128) NOT NULL,
`resourceIds` varchar(128) default NULL,
`appSecret` varchar(128) default NULL,
`scope` varchar(128) default NULL,
`grantTypes` varchar(128) default NULL,
`redirectUrl` varchar(128) default NULL,
`authorities` varchar(128) default NULL,
`access_token_validity` int(11) default NULL,
`refresh_token_validity` int(11) default NULL,
`additionalInformation` varchar(4096) default NULL,
`autoApproveScopes` varchar(128) default NULL,
PRIMARY KEY (`appId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for credentials
-- ----------------------------
DROP TABLE IF EXISTS `credentials`;
CREATE TABLE `credentials` (
`id` bigint(11) NOT NULL COMMENT '凭证id',
`enabled` tinyint(1) NOT NULL COMMENT '是否可用',
`name` varchar(255) NOT NULL COMMENT '用户名',
`password` varchar(255) NOT NULL COMMENT '密码',
`version` int(11) default NULL COMMENT '版本号',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for credentials_authorities
-- ----------------------------
DROP TABLE IF EXISTS `credentials_authorities`;
CREATE TABLE `credentials_authorities` (
`credentials_id` bigint(20) NOT NULL COMMENT '凭证id',
`authorities_id` bigint(20) NOT NULL COMMENT '权限id'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for oauth_access_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_access_token`;
CREATE TABLE `oauth_access_token` (
`token_id` varchar(128) default NULL,
`token` blob,
`authentication_id` varchar(128) NOT NULL,
`user_name` varchar(128) default NULL,
`client_id` varchar(128) default NULL,
`authentication` blob,
`refresh_token` varchar(128) default NULL,
PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for oauth_approvals
-- ----------------------------
DROP TABLE IF EXISTS `oauth_approvals`;
CREATE TABLE `oauth_approvals` (
`userId` varchar(128) default NULL,
`clientId` varchar(128) default NULL,
`scope` varchar(128) default NULL,
`status` varchar(10) default NULL,
`expiresAt` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
`lastModifiedAt` timestamp NOT NULL default '0000-00-00 00:00:00'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details` (
`client_id` varchar(128) NOT NULL,
`resource_ids` varchar(128) default NULL,
`client_secret` varchar(128) default NULL,
`scope` varchar(128) default NULL,
`authorized_grant_types` varchar(128) default NULL,
`web_server_redirect_uri` varchar(128) default NULL,
`authorities` varchar(128) default NULL,
`access_token_validity` int(11) default NULL,
`refresh_token_validity` int(11) default NULL,
`additional_information` varchar(4096) default NULL,
`autoapprove` varchar(128) default NULL,
PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for oauth_client_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_token`;
CREATE TABLE `oauth_client_token` (
`token_id` varchar(128) default NULL,
`token` blob,
`authentication_id` varchar(128) NOT NULL,
`user_name` varchar(128) default NULL,
`client_id` varchar(128) default NULL,
PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for oauth_code
-- ----------------------------
DROP TABLE IF EXISTS `oauth_code`;
CREATE TABLE `oauth_code` (
`code` varchar(128) default NULL,
`authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for oauth_refresh_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_refresh_token`;
CREATE TABLE `oauth_refresh_token` (
`token_id` varchar(128) default NULL,
`token` blob,
`authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for sys_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu` (
`menu_id` int(11) NOT NULL auto_increment COMMENT '菜单ID',
`name` varchar(32) NOT NULL COMMENT '名称',
`url` varchar(128) default NULL COMMENT 'url',
`method` varchar(32) default NULL COMMENT '请求方法',
`create_time` timestamp NULL default NULL COMMENT '创建时间',
`update_time` timestamp NULL default NULL COMMENT '更新时间',
`create_user` int(11) default NULL COMMENT '创建用户',
`update_user` int(11) default NULL COMMENT '更新用户',
PRIMARY KEY (`menu_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='菜单权限表';
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`role_id` int(11) NOT NULL auto_increment,
`role_name` varchar(64) NOT NULL,
`role_desc` varchar(190) default NULL,
`create_time` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
`update_time` timestamp NULL default NULL,
`create_user` int(11) default NULL COMMENT '创建用户',
`update_user` int(11) default NULL COMMENT '更新用户',
PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
-- ----------------------------
-- Table structure for sys_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu` (
`id` int(11) NOT NULL auto_increment,
`role_id` int(11) NOT NULL COMMENT '角色id',
`menu_id` int(11) NOT NULL COMMENT '资源id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
-- ----------------------------
-- Table structure for sys_role_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_user`;
CREATE TABLE `sys_role_user` (
`id` int(11) NOT NULL auto_increment,
`user_id` int(11) NOT NULL COMMENT '用户id',
`role_id` int(11) NOT NULL COMMENT '角色id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`user_id` int(11) NOT NULL auto_increment COMMENT '主键ID',
`username` varchar(64) NOT NULL COMMENT '用户名',
`password` varchar(255) NOT NULL,
`create_time` timestamp NULL default NULL COMMENT '创建时间',
`update_time` timestamp NULL default NULL COMMENT '修改时间',
`create_user` int(11) default NULL COMMENT '创建用户',
`update_user` int(11) default NULL COMMENT '更新用户',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='用户表';
四、代码架构
五、核心类
WebSecurityConfig
主要是开启拦截,设置数据源UserDetailsService
package com.vesus.springbootoauth2.config;
import com.vesus.springbootoauth2.service.impl.CustomUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
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.method.configuration.GlobalMethodSecurityConfiguration;
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.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
UserDetailsService customUserService(){
return new CustomUserService();
}
//配置全局设置
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
//设置UserDetailsService以及密码规则
auth.userDetailsService(customUserService());
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/hello") ;
}
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean() ;
}
//开启全局方法拦截
@EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
public static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
}
ResourceServerConfiguration
资源配置和服务配置
package com.vesus.springbootoauth2.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
private Logger logger = LoggerFactory.getLogger(ResourceServerConfiguration.class);
@Autowired
private CustomAuthenticationEntryPoint customAuthenticationEntryPoint ;
@Bean
public CustomLogoutSuccessHandler customLogoutSuccessHandler(){
return new CustomLogoutSuccessHandler();
} ;
@Override
public void configure(HttpSecurity http) throws Exception {
logger.info("=========================111111111=========");
http.exceptionHandling()
.authenticationEntryPoint(customAuthenticationEntryPoint)
.and()
.logout()
.logoutUrl("/oauth/logout")
.logoutSuccessHandler(customLogoutSuccessHandler())
.and()
.authorizeRequests()
.antMatchers("/hello/","/oauth/token").permitAll()
.antMatchers("/**").authenticated();
}
}
AuthorizationServerConfiguration
配置存储token的方式及配置token验证的相关信息
package com.vesus.springbootoauth2.config;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
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.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter implements EnvironmentAware {
private Logger logger = LoggerFactory.getLogger(AuthorizationServerConfiguration.class);
private static final String ENV_OAUTH = "authentication.oauth.";
private static final String PROP_CLIENTID = "clientid";
private static final String PROP_SECRET = "secret";
private static final String PROP_TOKEN_VALIDITY_SECONDS = "tokenValidityInSeconds";
private RelaxedPropertyResolver propertyResolver ;
@Autowired
private DataSource dataSource ;
@Bean
public TokenStore tokenStore(){
//这个是基于JDBC的实现,令牌(Access Token)会保存到数据库
return new JdbcTokenStore(dataSource);
}
@Autowired
@Qualifier("authenticationManagerBean")//认证方式
private AuthenticationManager authenticationManager ;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore())
.authenticationManager(authenticationManager) ;
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()") //allow check token
.allowFormAuthenticationForClients();
}
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("yaoge")//client_id用来标识客户的Id
.secret("123456")//secret客户端安全码
.scopes("all") //允许授权范围
.authorities("ROLE_ADMIN","ROLE_USER")//客户端可以使用的权限
.authorizedGrantTypes("client_credentials","password", "refresh_token")//允许授权类型
.accessTokenValiditySeconds(1200)
.refreshTokenValiditySeconds(50000);
}
@Override
public void setEnvironment(Environment environment) {
//获取到前缀是"authentication.oauth." 的属性列表值.
this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_OAUTH);
}
}
CustomLogoutSuccessHandler
package com.vesus.springbootoauth2.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.authentication.AbstractAuthenticationTargetUrlRequestHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class CustomLogoutSuccessHandler extends AbstractAuthenticationTargetUrlRequestHandler implements LogoutSuccessHandler {
private static final String BEARER_AUTHENTICATION = "Bearer ";
private static final String HEADER_AUTHORIZATION = "authorization";
@Autowired
private TokenStore tokenStore ;
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
String token = request.getHeader(HEADER_AUTHORIZATION);
if (token!=null&&token.startsWith(BEARER_AUTHENTICATION)){
OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(token.split(" ")[0]);
if (oAuth2AccessToken!=null){
tokenStore.removeAccessToken(oAuth2AccessToken);
}
}
response.setStatus(HttpServletResponse.SC_OK);
}
}
CustomAuthenticationEntryPoint
package com.vesus.springbootoauth2.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final Logger log = LoggerFactory.getLogger(CustomAuthenticationEntryPoint.class);
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
log.info("Pre-authenticated entry point called. Rejecting access");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,"Access Denied");
}
}
application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/test?characterEncoding=utf8
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
spring.jpa.database=mysql
spring.jpa.show-sql=true
spring.jpa.hibernate.naming.strategy=org.hibernate.cfg.ImprovedNamingStrategy
#debug=true
authentication.oauth.clientid=clientid
authentication.oauth.secret=secret
authentication.oauth.tokenValidityInSeconds=18000
security.oauth2.resource.filter-order = 3
六、启动验证
先获取token
http://localhost:8080/oauth/token?username=user_1&password=123456&grant_type=password&client_id=yaoge&client_secret=1234
请求接口
http://localhost:8080/abc?access_token=046ca7ae-b6ec-41a4-ae7d-3bd95756d6bb
项目路径:https://gitee.com/zhouzhiyao2/springboot-oauth2.git