现在oauth的开放授权在互联网上被广泛应用。oauth2已经被很多企业使用。
之前使用spring-mvc完成过oauth2的搭建,还是挺复杂的,本身oauth2的实现不是很难,使用spring-mvc感觉完全和oauth2的设计初衷有些背离。
现在spring-boot正在快速的崛起,使用起来也是相当的便利。本文就介绍下spring-boot集成oauth2的案例。
由于本文所涉及到的oauth2是在spring-security的基础上进行集成的,如果对security不熟悉的请参考文章 spring-boot集成spring-security
源代码地址: https://gitee.com/majinding/TM-oauth2-demo.git
数据库脚本参见 https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql
本文提供了一份mysql的脚本,点击下载
参考链接地址: https://projects.spring.io/spring-security-oauth/docs/oauth2.html
- 构建一个简单的maven项目
- 在项目中增加spring-boot和security及oauth的依赖支持
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.majingjing.tm.oauth2</groupId>
<artifactId>tm-oauth-server</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.7.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<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>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 配置数据库和页面等配置参数
security.basic.enabled=false
server.session.timeout=300
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tm-oauth?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.max-idle=5
spring.datasource.max-wait=10000
spring.datasource.min-idle=2
spring.datasource.initial-size=3
spring.datasource.validation-query=SELECT 1
#spring.datasource.test-on-borrow=true
#spring.datasource.test-while-idle=true
spring.datasource.time-between-eviction-runs-millis=18800
spring.datasource.jdbc-interceptors=ConnectionState;SlowQueryReport(threshold=50)
spring.thymeleaf.cache=false
spring.thymeleaf.encoding=UTF-8
- 对AuthorizationServerConfigurerAdapter做一些自定义改造
@Configuration
public class TmAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private DataSource dataSource;
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
endpoints.tokenStore(tokenStore());
// 配置TokenServices参数
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(endpoints.getTokenStore());
tokenServices.setSupportRefreshToken(false);
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30)); // 30天
endpoints.tokenServices(tokenServices);
endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);// 允许get方式访问token
endpoints.pathMapping("/oauth/confirm_access", "/oauth/confirm_access_majj");
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
// oauthServer.checkTokenAccess("isAuthenticated()");
oauthServer.checkTokenAccess("permitAll()");
oauthServer.allowFormAuthenticationForClients();
}
@Bean
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetails());
}
}
- 自定义页面(这里提供了两种自定义页面的方式)
- 授权页面
//如上的配置
endpoints.pathMapping("/oauth/confirm_access", "/oauth/confirm_access_majj");
@Controller
public class TmOauthController {
private static final Logger log = LoggerFactory.getLogger(TmOauthController.class);
@RequestMapping("/oauth/confirm_access_majj")
public String oauth(Map<String, Object> map, HttpServletRequest request,Model model) {
log.info("confirm_access_majj 重定向-model: {}", map);
log.info("confirm_access_majj 重定向-request: {}", request);
if (map.containsKey("scopes") || request.getAttribute("scopes") != null) {
@SuppressWarnings("unchecked")
Map<String, String> scopes = (Map<String, String>) (map.containsKey("scopes") ? map.get("scopes") : request
.getAttribute("scopes"));
System.out.println(scopes);
model.addAttribute("scopes", scopes);
}
return "approval";
}
}
- 错误页面
@Controller
@SessionAttributes("authorizationRequest")
public class TmErrorController {
@RequestMapping("/oauth/error")
public String handleError(HttpServletRequest request) {
Map<String, Object> model = new HashMap<String, Object>();
Object error = request.getAttribute("error");
// The error summary may contain malicious user input,
// it needs to be escaped to prevent XSS
String errorSummary;
if (error instanceof OAuth2Exception) {
OAuth2Exception oauthError = (OAuth2Exception) error;
errorSummary = HtmlUtils.htmlEscape(oauthError.getSummary());
}
else {
errorSummary = "Unknown error";
}
model.put("errorSummary", errorSummary);
return "error";
}
}
- 认证提供者
@Component
public class TmAuthenticationProvider implements AuthenticationProvider {
private static final Logger log = LoggerFactory.getLogger(TmAuthenticationProvider.class);
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
log.debug("authentication.Principal:{}",authentication.getPrincipal());
return new UsernamePasswordAuthenticationToken(authentication.getPrincipal(), authentication.getCredentials(),
Collections.<GrantedAuthority>emptyList());
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
- 配置启动类(增加@EnableAuthorizationServer)
@SpringBootApplication
@EnableAuthorizationServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Autowired
private AuthenticationProvider authenticationProvider;
@Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(authenticationProvider));
}
}
-
启动项目,打开浏览器访问如下链接
a。 授权
b。 获取令牌
c。 检查令牌
- http://localhost:8080/oauth/authorize?client_id=client-majj&response_type=code&redirect_uri=http://www.majj.cn:8083/client/user
- http://localhost:8080/oauth/token?client_id=client-majj&grant_type=authorization_code&redirect_uri=http://www.majj.cn:8083/client/user&code=iDjB32
- http://localhost:8080/oauth/check_token?token=0003077d-4470-447e-ac32-b5fc41d230e6
到此oauth-server已经搭建完成了。其实如果简单体验spring-boot来集成oauth2是非常简单的,本案例之所以看上去还是很复杂,是因为解释了如何使用spring提供的扩展点来完成自己的业务。