OAuth是一个关于授权的开放网络标准,在全世界得到的广泛的应用,目前是2.0的版本。OAuth2在“客户端”与“服务提供商”之间,设置了一个授权层(authorization layer)。“客户端”不能直接登录“服务提供商”,只能登录授权层,以此将用户与客户端分离。“客户端”登录需要OAuth提供的令牌,否则将提示认证失败而导致客户端无法访问服务。
OAuth2授权方式:
1、授权码模式(authorization code)
2、简化模式(implicit)
3、密码模式(resource owner password credentials)
4、客户端模式(client credentials)
授权码模式 授权码相对其他三种来说是功能比较完整、流程最安全严谨的授权方式,通过客户端的后台服务器与服务提供商的认证服务器交互来完成。
简化模式:
这种模式不通过服务器端程序来完成,直接由浏览器发送请求获取令牌,令牌是完全暴露在浏览器中的,这种模式极力不推崇
密码模式:
密码模式也是比较常用到的一种,客户端向授权服务器提供用户名、密码然后得到授权令牌。这种模式不过有种弊端,我们的客户端需要存储用户输入的密码,但是对于用户来说信任度不高的平台是不可能让他们输入密码的。
客户端模式:
客户端模式是客户端以自己的名义去授权服务器申请授权令牌,并不是完全意义上的授权。
初始化信息
DROP TABLE IF EXISTS `authority`;
CREATE TABLE `authority` (
`name` varchar(50) NOT NULL,
PRIMARY KEY (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of authority
-- ----------------------------
INSERT INTO `authority` VALUES ('ROLE_ADMIN');
INSERT INTO `authority` VALUES ('ROLE_USER');
-- ----------------------------
-- Table structure for oauth_access_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_access_token`;
CREATE TABLE `oauth_access_token` (
`token_id` varchar(256) DEFAULT NULL,
`token` blob,
`authentication_id` varchar(256) DEFAULT NULL,
`user_name` varchar(256) DEFAULT NULL,
`client_id` varchar(256) DEFAULT NULL,
`authentication` blob,
`refresh_token` varchar(256) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of oauth_access_token
-- ----------------------------
-- ----------------------------
-- Table structure for oauth_refresh_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_refresh_token`;
CREATE TABLE `oauth_refresh_token` (
`token_id` varchar(256) DEFAULT NULL,
`token` blob,
`authentication` blob
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of oauth_refresh_token
-- ----------------------------
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`username` varchar(50) NOT NULL,
`email` varchar(50) DEFAULT NULL,
`password` varchar(500) DEFAULT NULL,
`activated` tinyint(1) DEFAULT '0',
`activationkey` varchar(50) DEFAULT NULL,
`resetpasswordkey` varchar(50) DEFAULT NULL,
PRIMARY KEY (`username`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('admin', 'admin@mail.me', 'b8f57d6d6ec0a60dfe2e20182d4615b12e321cad9e2979e0b9f81e0d6eda78ad9b6dcfe53e4e22d1', '1', null, null);
INSERT INTO `user` VALUES ('user', 'user@mail.me', 'd6dfa9ff45e03b161e7f680f35d90d5ef51d243c2a8285aa7e11247bc2c92acde0c2bb626b1fac74', '1', null, null);
INSERT INTO `user` VALUES ('rajith', 'rajith@abc.com', 'd6dfa9ff45e03b161e7f680f35d90d5ef51d243c2a8285aa7e11247bc2c92acde0c2bb626b1fac74', '1', null, null);
-- ----------------------------
-- Table structure for user_authority
-- ----------------------------
DROP TABLE IF EXISTS `user_authority`;
CREATE TABLE `user_authority` (
`username` varchar(50) NOT NULL,
`authority` varchar(50) NOT NULL,
UNIQUE KEY `user_authority_idx_1` (`username`,`authority`),
KEY `authority` (`authority`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user_authority
-- ----------------------------
INSERT INTO `user_authority` VALUES ('admin', 'ROLE_ADMIN');
INSERT INTO `user_authority` VALUES ('admin', 'ROLE_USER');
INSERT INTO `user_authority` VALUES ('rajith', 'ROLE_USER');
INSERT INTO `user_authority` VALUES ('user', 'ROLE_USER');
引入依赖
<!--SpringSecurity-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
config
authentication.oauth.clientid=yuqiyu_home_pc
authentication.oauth.secret=yuqiyu_secret
authentication.oauth.tokenValidityInSeconds=1800
security.oauth2.resource.filter-order = 3
Model
Model和JpaRepository省略。。。。。。
public enum Authorities {
ROLE_ANONYMOUS,
ROLE_USER,
ROLE_ADMIN
}
@Component("userDetailsService")
public class UserDetailsService implements UserDetailsService {
@Autowired
private UserJPA userRepository;
@Override
@Transactional
public UserDetails loadUserByUsername(final String login) {
String lowercaseLogin = login.toLowerCase();
User userFromDatabase = userRepository.findByUsernameCaseInsensitive(lowercaseLogin);
if (userFromDatabase == null) {
throw new NewUserNotFoundException("User " + lowercaseLogin + " was not found in the database");
}
//获取用户的所有权限并且SpringSecurity需要的集合
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
for (Authority authority : userFromDatabase.getAuthorities()) {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(authority.getName());
grantedAuthorities.add(grantedAuthority);
}
//返回一个SpringSecurity需要的用户对象
return new org.springframework.security.core.userdetails.User(
userFromDatabase.getUsername(),
userFromDatabase.getPassword(),
grantedAuthorities);
}
}
SecurityConfiguration
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
//自定义UserDetailsService注入
@Autowired
private HengYuUserDetailsService userDetailsService;
//配置匹配用户时密码规则
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
//配置全局设置
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
//设置UserDetailsService以及密码规则
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
//排除/hello路径拦截
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/hello");
}
@Override
@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();
}
}
}
OAuth2Configuration
@Configuration
public class OAuth2Configuration {
@Configuration
@EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Autowired
private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
@Autowired
private CustomLogoutSuccessHandler customLogoutSuccessHandler;
@Override
public void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(customAuthenticationEntryPoint)
.and()
.logout()
.logoutUrl("/oauth/logout")
.logoutSuccessHandler(customLogoutSuccessHandler)
.and()
.authorizeRequests()
.antMatchers("/hello/").permitAll()
.antMatchers("/secure/**").authenticated();
}
}
@Configuration
@EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter implements EnvironmentAware {
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() {
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(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient(propertyResolver.getProperty(PROP_CLIENTID))
.scopes("read", "write")
.authorities(Authorities.ROLE_ADMIN.name(), Authorities.ROLE_USER.name())
.authorizedGrantTypes("password", "refresh_token")
.secret(propertyResolver.getProperty(PROP_SECRET))
.accessTokenValiditySeconds(propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer.class, 1800));
}
@Override
public void setEnvironment(Environment environment) {
this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_OAUTH);
}
}
}
CustomLogoutSuccessHandler
登出控制清空accessToken
@Component
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
自定义401错误码内容
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final Logger log = LoggerFactory.getLogger(CustomAuthenticationEntryPoint.class);
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException ae) throws IOException, ServletException {
log.info("Pre-authenticated entry point called. Rejecting access");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Access Denied");
}
}
AccessToken操作
- 获取token
访问/oauth/token地址获取access_token
可以看到我们访问的地址,grant_type使用到了password模式,我们在上面的配置中就是配置我们的客户端(yuqiyu_home_pc)可以执行的模式有两种:password、refresh_token。获取access_token需要添加客户端的授权信息clientid、secret,通过Postman工具的头授权信息即可输出对应的值就可以完成Basic Auth的加密串生成。
成功访问后oauth2给我们返回了几个参数:
access_token:本地访问获取到的access_token,会自动写入到数据库中。
token_type:获取到的access_token的授权方式
refersh_token:刷新token时所用到的授权token
expires_in:有效期(从获取开始计时,值秒后过期)
scope:客户端的接口操作权限(read:读,write:写)