From d129ae8ae59f106b754231bba7a7e4937b4aaa43 Mon Sep 17 00:00:00 2001 From: frankolschewski Date: Sat, 18 Aug 2018 13:06:57 +0200 Subject: [PATCH 1/3] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1298f7d..74cde6b 100644 --- a/README.md +++ b/README.md @@ -1 +1,2 @@ oauth2 +Full Article: From dd131e67fa43e7b734b57650e82b0c5be264ba68 Mon Sep 17 00:00:00 2001 From: frankolschewski Date: Sat, 18 Aug 2018 13:07:54 +0200 Subject: [PATCH 2/3] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 74cde6b..b8132a4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ oauth2 + Full Article: From 8890e1d8e4ba292d956823328ac9b1f857d758d4 Mon Sep 17 00:00:00 2001 From: Frank Olschewski Date: Thu, 23 Aug 2018 12:10:59 +0200 Subject: [PATCH 3/3] some code improvements, check_token endpoint fix, switch from mysql to h2, apache jmeter testfile added --- authorization_server/.gitignore | 4 +- authorization_server/build.gradle | 75 ++ .../aak/AuthorizationServerApplication.java | 16 - .../java/com/aak/api/ClientsController.java | 15 +- .../java/com/aak/api/LoginController.java | 2 +- .../AuthorizationServerConfiguration.java | 6 +- .../aak/configuration/JdbcUserDetails.java | 22 +- .../configuration/SplitCollectionEditor.java | 3 +- .../WebSecurityConfiguration.java | 5 +- .../src/main/resources/application.yml | 36 +- .../src/main/resources/data.sql | 17 +- .../src/main/resources/schema.sql | 1 - .../src/test/resources/oauth2_flows.jmx | 937 ++++++++++++++++++ 13 files changed, 1064 insertions(+), 75 deletions(-) create mode 100755 authorization_server/build.gradle create mode 100644 authorization_server/src/test/resources/oauth2_flows.jmx diff --git a/authorization_server/.gitignore b/authorization_server/.gitignore index 82eca33..8d5a313 100644 --- a/authorization_server/.gitignore +++ b/authorization_server/.gitignore @@ -15,6 +15,8 @@ *.iws *.iml *.ipr +.gradle +/out/ ### NetBeans ### /nbproject/private/ @@ -22,4 +24,4 @@ /nbbuild/ /dist/ /nbdist/ -/.nb-gradle/ \ No newline at end of file +/.nb-gradle/ diff --git a/authorization_server/build.gradle b/authorization_server/build.gradle new file mode 100755 index 0000000..f72e43d --- /dev/null +++ b/authorization_server/build.gradle @@ -0,0 +1,75 @@ +apply plugin: 'java' +apply plugin: 'idea' +apply plugin: 'org.springframework.boot' +apply plugin: 'io.spring.dependency-management' + +tasks.withType(JavaCompile) { + options.encoding = "UTF-8" +} + +buildscript { + repositories { + mavenLocal() + mavenCentral() + } + dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:2.0.4.RELEASE") + } +} + +group 'com.aak' +version '0.0.2-SNAPSHOT' + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +jar { + baseName = 'authorization_server' + version = '0.0.2' + from "gradle.properties" +} + +springBoot { + mainClassName = "com.aak.AuthorizationServerApplication" + +} + +idea { + project { + languageLevel = '1.8' + } + module { + downloadJavadoc = true + downloadSources = true + } +} + +configurations { + compile.exclude module: 'spring-boot-starter-tomcat' // we exchange tomcat with jetty +} + +repositories { + mavenLocal() + mavenCentral() +} + +dependencies { + compile 'org.springframework.boot:spring-boot-devtools' + compile 'org.springframework.boot:spring-boot-starter-actuator' + compile 'org.springframework.boot:spring-boot-starter-jetty' + compile 'org.springframework.boot:spring-boot-starter-data-rest' + compile 'org.springframework.boot:spring-boot-starter-security' + compile 'org.springframework.boot:spring-boot-starter-thymeleaf' + compile 'org.springframework.boot:spring-boot-starter-web' + compile 'org.springframework.boot:spring-boot-starter-data-jpa' + compile 'org.springframework.boot:spring-boot-starter-jdbc' + compile 'com.h2database:h2' + compile 'org.springframework.cloud:spring-cloud-starter-oauth2:2.0.0.RELEASE' + compile 'org.webjars:bootstrap:4.1.3' + + compile 'org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.0.4.RELEASE' + compile 'org.springframework.security.oauth:spring-security-oauth2:2.3.3.RELEASE' + + testCompile 'org.springframework.boot:spring-boot-starter-test' + testCompile 'org.springframework.security:spring-security-test' +} diff --git a/authorization_server/src/main/java/com/aak/AuthorizationServerApplication.java b/authorization_server/src/main/java/com/aak/AuthorizationServerApplication.java index d41c808..01a3e93 100644 --- a/authorization_server/src/main/java/com/aak/AuthorizationServerApplication.java +++ b/authorization_server/src/main/java/com/aak/AuthorizationServerApplication.java @@ -1,19 +1,10 @@ package com.aak; -import com.zaxxer.hikari.HikariDataSource; -import org.apache.catalina.servlets.WebdavServlet; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.jdbc.DataSourceBuilder; -import org.springframework.boot.web.servlet.ServletRegistrationBean; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; - -import javax.sql.DataSource; @SpringBootApplication @@ -22,13 +13,6 @@ @ComponentScan public class AuthorizationServerApplication { - @Bean - @Primary - @ConfigurationProperties(prefix = "spring.datasource") - public DataSource mainDataSource() { - return DataSourceBuilder.create().type(HikariDataSource.class).build(); - } - public static void main(String[] args) { SpringApplication.run(AuthorizationServerApplication.class, args); } diff --git a/authorization_server/src/main/java/com/aak/api/ClientsController.java b/authorization_server/src/main/java/com/aak/api/ClientsController.java index 7eb1b48..05db9a3 100644 --- a/authorization_server/src/main/java/com/aak/api/ClientsController.java +++ b/authorization_server/src/main/java/com/aak/api/ClientsController.java @@ -5,7 +5,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; import org.springframework.stereotype.Controller; @@ -14,6 +13,7 @@ import org.springframework.web.bind.annotation.*; import java.util.Collection; +import java.util.Optional; import java.util.Set; /** @@ -36,16 +36,9 @@ public void initBinder(WebDataBinder binder){ @RequestMapping(value="/form",method= RequestMethod.GET) @PreAuthorize("hasRole('ROLE_OAUTH_ADMIN')") public String showEditForm(@RequestParam(value="client",required=false)String clientId, Model model){ - - ClientDetails clientDetails; - if(clientId !=null){ - clientDetails=clientsDetailsService.loadClientByClientId(clientId); - } - else{ - clientDetails =new BaseClientDetails(); - } - - model.addAttribute("clientDetails",clientDetails); + model.addAttribute("clientDetails", Optional.ofNullable(clientId) + .map(clientsDetailsService::loadClientByClientId) + .orElse(new BaseClientDetails())); return "form"; } diff --git a/authorization_server/src/main/java/com/aak/api/LoginController.java b/authorization_server/src/main/java/com/aak/api/LoginController.java index ca7a5fd..13bfb4e 100644 --- a/authorization_server/src/main/java/com/aak/api/LoginController.java +++ b/authorization_server/src/main/java/com/aak/api/LoginController.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; + import static java.util.Arrays.asList; /** @@ -67,7 +68,6 @@ public String loginPage() { } - @RequestMapping(value="/logout", method = RequestMethod.GET) public String logoutPage (HttpServletRequest request, HttpServletResponse response) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); diff --git a/authorization_server/src/main/java/com/aak/configuration/AuthorizationServerConfiguration.java b/authorization_server/src/main/java/com/aak/configuration/AuthorizationServerConfiguration.java index b625bab..65328a3 100644 --- a/authorization_server/src/main/java/com/aak/configuration/AuthorizationServerConfiguration.java +++ b/authorization_server/src/main/java/com/aak/configuration/AuthorizationServerConfiguration.java @@ -58,7 +58,11 @@ public void configure(ClientDetailsServiceConfigurer clients) throws Exception { @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { - + // grant access to token_key and check_token endpoints for authenticated user + // isAuthenticated() because all clients are trusted clients + // see: http://projects.spring.io/spring-security-oauth/docs/oauth2.html#resource-server-configuration + oauthServer.tokenKeyAccess("isAuthenticated()") + .checkTokenAccess("isAuthenticated()"); } @Override diff --git a/authorization_server/src/main/java/com/aak/configuration/JdbcUserDetails.java b/authorization_server/src/main/java/com/aak/configuration/JdbcUserDetails.java index 7727b09..21ee3c2 100644 --- a/authorization_server/src/main/java/com/aak/configuration/JdbcUserDetails.java +++ b/authorization_server/src/main/java/com/aak/configuration/JdbcUserDetails.java @@ -1,14 +1,13 @@ package com.aak.configuration; -import com.aak.domain.Credentials; import com.aak.repository.CredentialRepository; import org.springframework.beans.factory.annotation.Autowired; 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.security.crypto.factory.PasswordEncoderFactories; -import org.springframework.security.crypto.password.PasswordEncoder; + +import java.util.Optional; /** * Created by ahmed on 21.5.18. @@ -20,18 +19,9 @@ public class JdbcUserDetails implements UserDetailsService{ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - Credentials credentials = credentialRepository.findByName(username); - - - if(credentials==null){ - - throw new UsernameNotFoundException("User"+username+"can not be found"); - } - - User user = new User(credentials.getName(),credentials.getPassword(),credentials.isEnabled(),true,true,true,credentials.getAuthorities()); - - return user; - - + return Optional.ofNullable(username) + .map(credentialRepository::findByName) + .map(i -> new User(i.getName(),i.getPassword(),i.isEnabled(),true,true,true,i.getAuthorities())) + .orElseThrow(() -> new UsernameNotFoundException("User"+String.valueOf(username)+"can not be found")); } } diff --git a/authorization_server/src/main/java/com/aak/configuration/SplitCollectionEditor.java b/authorization_server/src/main/java/com/aak/configuration/SplitCollectionEditor.java index 61dcf6b..ebeadb5 100644 --- a/authorization_server/src/main/java/com/aak/configuration/SplitCollectionEditor.java +++ b/authorization_server/src/main/java/com/aak/configuration/SplitCollectionEditor.java @@ -1,6 +1,7 @@ package com.aak.configuration; import org.springframework.beans.propertyeditors.CustomCollectionEditor; +import org.springframework.util.StringUtils; import java.util.Collection; @@ -20,7 +21,7 @@ public SplitCollectionEditor(Class collectionType, String @Override public void setAsText(String text) throws IllegalArgumentException { - if (text == null || text.isEmpty()) { + if (StringUtils.isEmpty(text)) { super.setValue(super.createCollection(this.collectionType, 0)); } else { super.setValue(text.split(splitRegex)); diff --git a/authorization_server/src/main/java/com/aak/configuration/WebSecurityConfiguration.java b/authorization_server/src/main/java/com/aak/configuration/WebSecurityConfiguration.java index cd4606f..9b82d9e 100644 --- a/authorization_server/src/main/java/com/aak/configuration/WebSecurityConfiguration.java +++ b/authorization_server/src/main/java/com/aak/configuration/WebSecurityConfiguration.java @@ -10,7 +10,6 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.provisioning.JdbcUserDetailsManager; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; /** @@ -34,7 +33,9 @@ public UserDetailsService userDetailsServiceBean() throws Exception { @Override public void configure(WebSecurity web) throws Exception { - web.ignoring().antMatchers("/webjars/**","/resources/**"); + web.ignoring().antMatchers("/webjars/**","/resources/**" + ,"/h2-console/**","/actuator/**" // <-- remove this in production code + ); } diff --git a/authorization_server/src/main/resources/application.yml b/authorization_server/src/main/resources/application.yml index 5712dbd..cdcf02d 100644 --- a/authorization_server/src/main/resources/application.yml +++ b/authorization_server/src/main/resources/application.yml @@ -1,20 +1,24 @@ -spring: - datasource: - hikari: - connection-test-query: SELECT 1 FROM DUAL - minimum-idle: 1 - maximum-pool-size: 5 - driver-class-name: com.mysql.jdbc.Driver - jdbc-url: jdbc:mysql://localhost/oauth2 - username: root - password: - initialization-mode: always - jpa: - hibernate: - ddl-auto: none -# --- server server: port: 8081 +spring: + jackson: + serialization: + INDENT_OUTPUT: true + h2: + console: + enabled: true + datasource: + jdbc-url: jdbc:h2:mem:oauth;DB_CLOSE_DELAY=-1 + driver-class-name: org.h2.Driver + username: root + password: + initialization-mode: always + jpa: + hibernate: + ddl-auto: none + database-platform: org.hibernate.dialect.H2Dialect - +logging: + level: + org.springframework.security: DEBUG diff --git a/authorization_server/src/main/resources/data.sql b/authorization_server/src/main/resources/data.sql index 46624c3..091c4e5 100644 --- a/authorization_server/src/main/resources/data.sql +++ b/authorization_server/src/main/resources/data.sql @@ -1,12 +1,11 @@ -INSERT INTO authority VALUES(1,'ROLE_OAUTH_ADMIN'); +INSERT INTO authority VALUES(1,'ROLE_OAUTH_ADMIN'); INSERT INTO authority VALUES(2,'ROLE_RESOURCE_ADMIN'); INSERT INTO authority VALUES(3,'ROLE_PRODUCT_ADMIN'); -INSERT INTO credentials VALUES(1,b'1','oauth_admin','$2a$10$BurTWIy5NTF9GJJH4magz.9Bd4bBurWYG8tmXxeQh1vs7r/wnCFG2','0'); -INSERT INTO credentials VALUES(2,b'1','resource_admin','$2a$10$BurTWIy5NTF9GJJH4magz.9Bd4bBurWYG8tmXxeQh1vs7r/wnCFG2','0'); -INSERT INTO credentials VALUES(3,b'1','product_admin','$2a$10$BurTWIy5NTF9GJJH4magz.9Bd4bBurWYG8tmXxeQh1vs7r/wnCFG2','0'); -INSERT INTO credentials_authorities VALUE (1,1); -INSERT INTO credentials_authorities VALUE (2,2); -INSERT INTO credentials_authorities VALUE (3,3); +INSERT INTO credentials VALUES(1,'1','oauth_admin','$2a$10$BurTWIy5NTF9GJJH4magz.9Bd4bBurWYG8tmXxeQh1vs7r/wnCFG2','0'); +INSERT INTO credentials VALUES(2,'1','resource_admin','$2a$10$BurTWIy5NTF9GJJH4magz.9Bd4bBurWYG8tmXxeQh1vs7r/wnCFG2','0'); +INSERT INTO credentials VALUES(3,'1','product_admin','$2a$10$BurTWIy5NTF9GJJH4magz.9Bd4bBurWYG8tmXxeQh1vs7r/wnCFG2','0'); +INSERT INTO credentials_authorities VALUES (1,1); +INSERT INTO credentials_authorities VALUES (2,2); +INSERT INTO credentials_authorities VALUES (3,3); - -INSERT INTO oauth_client_details VALUES('curl_client','product_api', '$2a$10$BurTWIy5NTF9GJJH4magz.9Bd4bBurWYG8tmXxeQh1vs7r/wnCFG2', 'read,write', 'client_credentials', 'http://127.0.0.1', 'ROLE_PRODUCT_ADMIN', 7200, 0, NULL, 'true'); +INSERT INTO oauth_client_details VALUES('curl_client','product_api', '$2a$10$BurTWIy5NTF9GJJH4magz.9Bd4bBurWYG8tmXxeQh1vs7r/wnCFG2', 'read,write', 'password,client_credentials,authorization_code,refresh_token', 'http://127.0.0.1', 'ROLE_PRODUCT_ADMIN', 7200, 0, NULL, 'true'); diff --git a/authorization_server/src/main/resources/schema.sql b/authorization_server/src/main/resources/schema.sql index 999ca6d..094680b 100644 --- a/authorization_server/src/main/resources/schema.sql +++ b/authorization_server/src/main/resources/schema.sql @@ -43,7 +43,6 @@ CREATE TABLE authority ( primary key (id) ); drop table if exists credentials; - CREATE TABLE credentials ( id integer, enabled boolean not null, diff --git a/authorization_server/src/test/resources/oauth2_flows.jmx b/authorization_server/src/test/resources/oauth2_flows.jmx new file mode 100644 index 0000000..e9d0d6b --- /dev/null +++ b/authorization_server/src/test/resources/oauth2_flows.jmx @@ -0,0 +1,937 @@ + + + + + + false + true + false + + + + + + + + continue + + false + 1 + + 1 + 1 + false + + + + + + + + server_domain + localhost + = + + + server_port + 8081 + = + + + access_token + undefined + = + + + refresh_token + undefined + = + + + redirect_location + undefined + = + + + client_id + curl_client + = + + + client_secret + user + = + + + oauth_username + oauth_admin + = + + + oauth_password + user + = + + + authorization_code + undefined + = + + + authorization_state + undefined + = + + + csrf + undefined + = + + + + + + + false + + + + + + + false + password + = + true + grant_type + + + false + ${oauth_username} + = + true + username + + + false + ${oauth_password} + = + true + password + + + false + write + = + true + scope + + + false + ${client_id} + = + true + client_id + + + false + ${client_secret} + = + true + client_secret + + + + ${server_domain} + ${server_port} + + + oauth/token + POST + false + false + false + false + + + + Get access token with client credentials + + + + + + Content-Type + application/x-www-form-urlencoded; charset=utf-8 + + + + + + + 200 + + + Assertion.response_code + false + 16 + + + + $.token_type + bearer + false + false + false + true + + + + access_token + $.access_token + + all + access_token + parse-error + + + + + + + + false + password + = + true + grant_type + + + false + ${oauth_username} + = + true + username + + + false + ${oauth_password} + = + true + password + + + false + write + = + true + scope + + + + ${server_domain} + ${server_port} + + + oauth/token + POST + false + false + false + false + + + + Get access token with client credentials + + + + + + http://${server_domain}:${server_port}/oauth + ${client_id} + ${client_secret} + + oauth/token + + + + + + + + Content-Type + application/x-www-form-urlencoded; charset=utf-8 + + + + + + + 200 + + + Assertion.response_code + false + 16 + + + + $.token_type + bearer + false + false + false + true + + + + access_token + $.access_token + + all + access_token + parse-error + + + + refresh_token + $.refresh_token + + all + access_token + parse-error + + + + + + + + false + ${access_token} + = + true + token + + + + ${server_domain} + ${server_port} + + + oauth/check_token + GET + false + false + false + false + + + + Check the Access Token from the previous request + + + + + + http://${server_domain}:${server_port}/oauth + ${client_id} + ${client_secret} + + oauth/token + + + + + + + 200 + + + Assertion.response_code + false + 16 + + + + $.client_id + test + false + false + false + true + + + + + + + + false + refresh_token + = + true + grant_type + + + false + ${refresh_token} + = + true + refresh_token + + + false + write + = + true + scope + + + + ${server_domain} + ${server_port} + + + oauth/token + POST + false + false + false + false + + + + Get access token with resource owner credentials + + + + + + http://${server_domain}:${server_port}/oauth + ${client_id} + ${client_secret} + + oauth/token + + + + + + + + Content-Type + application/x-www-form-urlencoded; charset=utf-8 + + + + + + + 200 + + + Assertion.response_code + false + 16 + + + + $.token_type + bearer + false + false + false + true + + + + access_token + $.access_token + + all + access_token + parse-error + + + + refresh_token + $.refresh_token + + all + access_token + parse-error + + + + + + + + false + ${access_token} + = + true + token + + + + ${server_domain} + ${server_port} + + + oauth/check_token + GET + false + false + false + false + + + + Check the Access Token from the previous request + + + + + + http://${server_domain}:${server_port}/oauth + ${client_id} + ${client_secret} + + oauth/token + + + + + + + 200 + + + Assertion.response_code + false + 16 + + + + $.client_id + test + false + false + false + true + + + + + + + + false + client_credentials + = + true + grant_type + + + false + write + = + true + scope + + + + ${server_domain} + ${server_port} + + + oauth/token + POST + false + false + false + false + + + + Get access token with client credentials + + + + + + http://${server_domain}:${server_port}/oauth + ${client_id} + ${client_secret} + + oauth/token + + + + + + + + Content-Type + application/x-www-form-urlencoded; charset=utf-8 + + + + + + + 200 + + + Assertion.response_code + false + 16 + + + + $.token_type + bearer + false + false + false + true + + + + access_token + $.access_token + + all + access_token + parse-error + + + + + + + + false + ${access_token} + = + true + token + + + + ${server_domain} + ${server_port} + + + oauth/check_token + GET + false + false + false + false + + + + Check the Access Token from the previous request + + + + + + http://${server_domain}:${server_port}/oauth + ${client_id} + ${client_secret} + + oauth/token + + + + + + + 200 + + + Assertion.response_code + false + 16 + + + + $.client_id + test + false + false + false + true + + + + + + + + false + code + = + true + response_type + + + false + ${client_id} + = + true + client_id + + + false + localhost + = + true + redirect_uri + + + false + write + = + true + scope + + + false + xyz + = + true + state + + + + ${server_domain} + ${server_port} + + + oauth/authorize + GET + false + false + false + false + + + + Call Authorization Code grand and expect login form + + + + + 302 + + + Assertion.response_code + false + 16 + + + + true + redirect_location + http[s]?://${server_domain}:${server_port}/(.+) + $1$ + + + all + + + + + + + + ${server_domain} + ${server_port} + + + ${redirect_location} + GET + false + false + false + false + + + + Load login page + + + + + 200 + + + Assertion.response_code + false + 16 + + + + false + csrf + name="_csrf" value="([^"]+)" + $1$ + + + all + + + + + + + + false + + = + true + + + + ${server_domain} + ${server_port} + + + ${redirect_location} + POST + false + false + false + false + + + + Load login page + + + + + + Content-Type + application/x-www-form-urlencoded; charset=utf-8 + + + + + + + 200 + + + Assertion.response_code + false + 16 + + + + authorization_code + $.code + + all + access_token + parse-error + + + + authorization_state + $.state + + all + access_token + parse-error + + + + + false + true + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + +