参考了Springboot2+SpringSecurity+Oauth2+Mysql数据库实现持久化客户端数据
1 基本环境搭建
1.1 数据库脚本
数据库脚本从官方spring-security-oauth中获取,根据你需要创建对应的表.我这里用到了下面几张表。
1.2 ouath2.0相关jar
引入对应的jar,与security和oauth2.0相关的
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
<version>2.3.12.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-oauth2artifactId>
<version>2.2.5.RELEASEversion>
dependency>
1.3 spring security相关配置
import com.dzmsoft.open.oauth.service.UserService;
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.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.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
@Autowired
private PasswordEncoder passwordEncoder;
@Bean
public PasswordEncoder myPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 匹配下面的路径放过
.antMatchers("/test/01").permitAll()
// 资源必须授权后访问
.anyRequest().authenticated()
.and()
.formLogin()
.loginProcessingUrl("/login")
.permitAll()//指定认证页面可以匿名访问
//关闭跨站请求防护
.and()
.httpBasic()
.and()
.csrf().disable();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
//UserDetailsService类
auth.userDetailsService(userService)
//加密策略
.passwordEncoder(passwordEncoder);
}
}
1.4 授权服务器配置
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
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.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.JdbcApprovalStore;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import javax.sql.DataSource;
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
/**
* 从数据库中查询出客户端信息
* @return
*/
@Bean
public JdbcClientDetailsService clientDetailsService() {
JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
jdbcClientDetailsService.setPasswordEncoder(passwordEncoder);
return jdbcClientDetailsService;
}
/**
* token保存策略
* @return
*/
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
/**
* 授权信息保存策略
* @return
*/
@Bean
public ApprovalStore approvalStore() {
return new JdbcApprovalStore(dataSource);
}
/**
* 授权码模式专用对象
* @return
*/
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(dataSource);
}
@Bean
public CustomWebResponseExceptionTranslator myWebResponseExceptionTranslator(){
return new CustomWebResponseExceptionTranslator();
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService());
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//允许form表单客户端认证,允许客户端使用client_id和client_secret获取token
security
// .allowFormAuthenticationForClients()
//通过验证返回token信息
// .checkTokenAccess("isAuthenticated()")
.checkTokenAccess("permitAll()")
// 获取token请求不进行拦截
.tokenKeyAccess("permitAll()")
.passwordEncoder(passwordEncoder);
CustomClientCredentialsTokenEndpointFilter endpointFilter = new CustomClientCredentialsTokenEndpointFilter(security);
endpointFilter.afterPropertiesSet();
endpointFilter.setAuthenticationEntryPoint(customAuthenticationEntryPoint);
// 客户端认证之前的过滤器
security.addTokenEndpointAuthenticationFilter(endpointFilter);
}
/**
* OAuth2的主配置信息
* @return
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.approvalStore(approvalStore())
.authenticationManager(authenticationManager)
.authorizationCodeServices(authorizationCodeServices())
.tokenStore(tokenStore())
.userDetailsService(userDetailsService)
.exceptionTranslator(myWebResponseExceptionTranslator());
}
}
1.5 entrypoint
SpringBoot的Endpoint主要是用来监控应用服务的运行状况,并集成在Mvc中提供查看接口
浅析@ResponseBodyAdvice的理解以及实际应用,ResponseBodyAdvice 接口是在 Controller 执行 return 之后,在 response 返回给客户端之前,执行的对 response 的一些处理,可以实现对 response 数据的一些统一封装或者加密等操作
下面可以看到对返回值的修正。
import com.dzmsoft.open.oauth.dto.CommonResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.LinkedHashMap;
@Slf4j
@ControllerAdvice
public class CustomResponseBodyAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
//此处返回true,表示对任何handler的responsebody都调用beforeBodyWrite方法,如果有特殊方法不使用可以考虑使用注解等方式过滤
return true;
}
/**
* 对Controller的所有返回结果进行处理
* @param body 是controller方法中返回的值,对其进行修改后再return
* @param methodParameter
* @param mediaType
* @param aClass
* @param serverHttpRequest
* @param serverHttpResponse
* @return
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
log.info("请求返回数据类型class="+ body.getClass().getName());
if(body instanceof CommonResponse){
return body;
}
CommonResponse<Object> response = new CommonResponse<>();
if (body.toString().contains("error") && body.toString().contains("Unauthorized")){
if(body instanceof LinkedHashMap){
LinkedHashMap<String,String> map = (LinkedHashMap)body;
map.remove("timestamp");
map.remove("status");
map.remove("message");
response.fail(map);
}else{
response.fail(body);
}
}else{
response.success(body);
}
if (log.isDebugEnabled()) {
log.debug("请求返回数据body= " + body.toString());
}
return response;
}
}
异常返回
import com.alibaba.fastjson.JSON;
import com.dzmsoft.open.oauth.dto.CommonResponse;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component("customAuthenticationEntryPoint")
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.setStatus(HttpStatus.OK.value());
CommonResponse<Object> response1 = new CommonResponse<>();
response1.fail( String.valueOf(HttpStatus.UNAUTHORIZED.value()),"client_id或client_secret错误",null);
response.setHeader("Content-Type", "application/json;charset=utf-8");
response.getWriter().print(JSON.toJSONString(response1));
response.getWriter().flush();
}
}
1.6 FrameworkEndpoint
oauth2.0
等请求响应入口在spring-security-oauth2
的jar中,@frameworkendpoint是@Controller的同义词
当然进去之前,先会进入ClientCredentialsTokenEndpointFilter
中
2 oauth2.0的四种模式
2.1 授权码模式
2.2 简化模式
2.3 密码模式
2.4 客户端模式
3 调整token的存储