├── .gitignore
├── LICENSE
├── README.md
└── identity-server
├── pom.xml
└── src
└── main
├── java
└── com
│ └── hascode
│ └── tutorial
│ ├── DatabaseConfig.java
│ ├── Oauth2AuthorizationServerApplication.java
│ ├── OpenApiTokenEnhancer.java
│ ├── SecurityConfig.java
│ ├── auth
│ ├── JwtAuthFilter.java
│ ├── JwtAuthToken.java
│ ├── JwtAuthenticatedProfile.java
│ ├── JwtAuthenticationEntryPoint.java
│ ├── JwtAuthenticationProvider.java
│ ├── LoginCredentials.java
│ └── SecretKeyProvider.java
│ ├── domain
│ ├── DetailedProfile.java
│ ├── Login.java
│ ├── MinimalProfile.java
│ ├── Name.java
│ ├── Picture.java
│ └── Profile.java
│ ├── exceptions
│ ├── FailedToLoginException.java
│ ├── JwtAuthenticationException.java
│ └── ProfileNotFoundException.java
│ ├── service
│ ├── JwtService.java
│ ├── LoginService.java
│ └── ProfileService.java
│ └── web
│ ├── LoginController.java
│ ├── ProfileController.java
│ └── advices
│ └── GlobalExceptionHandler.java
└── resources
├── application.properties
├── jwt.key
├── profiles.json
└── schema.sql
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | **/.idea
3 | build_reports
4 | .project
5 | .classpath
6 | .settings/
7 | target/
8 | logs/
9 | bin/
10 | build/
11 | out/
12 | */*.class
13 | */**/*.class
14 | .metadata/
15 | classes/
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Nilath Jayamaha
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Spring Boot OAuth 2 with JWT authentication and JDBC client store
2 |
3 | This is based on the original project from:
4 | http://www.hascode.com/2016/03/setting-up-an-oauth2-authorization-server-and-resource-provider-with-spring-boot/
5 | Please refer to: **2016 Micha Kops / hasCode.com**
6 |
7 |
8 | ## Enhanced functionality
9 | Added JWT based authorization, OpenAPI access token enrichment, JDBC based client store.
10 |
11 | Pre-requisits:
12 | Createa Mysql database jdbc:mysql://localhost:3306/identity with user name root and blank password.
13 | This is configured in DatabaseConfig.
14 |
15 | 1. Start up com.hascode.tutorial.Oauth2AuthorizationServerApplication as a spring boot application.
16 |
17 | 2. POST to http://localhost:9000/login with Content-Type application/json and body {"username":"greenrabbit948", "password":"celeste"}
18 | A JWT will be returned in the header, with a success 200.
19 |
20 | 3. GET to http://localhost:9000/oauth/authorize?redirect_uri=http://localhost:8080/&client_id=ING_BANK&response_type=code
21 | with a header a Authorization Bearer jwt-token
22 | You will be redirected to localhost:8080 with an authorization code.
23 |
24 | 4. Use the authorization code
25 | POST http://localhost:9000/oauth/token?redirect_uri=http://localhost:8080/&grant_type=authorization_code&code=YOUR_AUTH_CODE_FROM_ABOVE
26 | 5. You will get an access token and an id_token in a json response
27 | ```
28 | {
29 | "access_token": "950d44c9-54b5-445f-8e82-02995d2468a0",
30 | "token_type": "bearer",
31 | "expires_in": 43199,
32 | "scope": "openid",
33 | "id_token": "ad9f151c-1296-4dc4-976b-3eaed9eb08dd"
34 | }
35 | ```
36 |
37 |
38 | ## Original Doco:
39 |
40 | Examples how to set up an OAuth2 identity server and resource provider within a few minutes using [Spring Boot] and Maven.
41 |
42 | Please feel free to take a look at [my blog] for the full tutorial.
43 |
44 | ## Running the Identity Server
45 |
46 | Using Maven
47 |
48 | ```
49 | cd identity-server && mvn spring-boot:run
50 | ```
51 |
52 | ## Running the Resource Provider
53 |
54 | Using Maven
55 |
56 | ```
57 | cd resource-provider && mvn spring-boot:run
58 | ```
59 |
60 | ## Requesting a Token
61 |
62 | Using Curl
63 |
64 | ```
65 | curl -XPOST -k -vi foo:foosecret@localhost:9000/hascode/oauth/token \
66 | -d grant_type=password -d client_id=foo -d client_secret=abc123 \
67 | -d redirect_uri=http://www.hascode.com -d username=bar -d password=barsecret
68 | ```
69 |
70 | ## Accessing the secured Resource
71 |
72 | ```
73 | TOKEN = 'xxxxxxx'
74 | curl -vi -H "Authorization: Bearer $TOKEN" http://localhost:9001/resource/
75 | ```
76 |
77 | ---
78 |
--------------------------------------------------------------------------------
/identity-server/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.hascode.tutorial
7 | spring-oath2-identity-server
8 | 0.0.1-SNAPSHOT
9 | jar
10 |
11 | spring-oath2-sample
12 | Demo project for Spring Boot
13 |
14 |
15 | org.springframework.boot
16 | spring-boot-starter-parent
17 | 1.3.1.RELEASE
18 |
19 |
20 |
21 |
22 | UTF-8
23 | 1.8
24 |
25 |
26 |
27 |
28 | org.springframework.security.oauth
29 | spring-security-oauth2
30 |
31 |
32 | org.springframework.boot
33 | spring-boot-starter-security
34 |
35 |
36 | org.springframework.security
37 | spring-security-jwt
38 |
39 |
40 | org.springframework.boot
41 | spring-boot-starter-web
42 |
43 |
44 | org.springframework.boot
45 | spring-boot-starter-data-jpa
46 |
47 |
48 | mysql
49 | mysql-connector-java
50 |
51 |
52 | org.projectlombok
53 | lombok
54 | 1.16.8
55 | provided
56 |
57 |
58 | io.jsonwebtoken
59 | jjwt
60 | 0.6.0
61 |
62 |
63 |
64 |
65 |
66 |
67 | org.springframework.boot
68 | spring-boot-maven-plugin
69 |
70 |
71 |
72 |
73 |
74 |
75 | spring-snapshots
76 | Spring Snapshots
77 | https://repo.spring.io/snapshot
78 |
79 | true
80 |
81 |
82 |
83 | spring-milestones
84 | Spring Milestones
85 | https://repo.spring.io/milestone
86 |
87 | false
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/DatabaseConfig.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial;
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.jdbc.datasource.DriverManagerDataSource;
6 |
7 | @Configuration
8 | public class DatabaseConfig {
9 |
10 | @Bean(name = "dataSource")
11 | public DriverManagerDataSource dataSource() {
12 | DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
13 | driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
14 | driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/identity");
15 | driverManagerDataSource.setUsername("root");
16 | driverManagerDataSource.setPassword(null);
17 | return driverManagerDataSource;
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/Oauth2AuthorizationServerApplication.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.boot.SpringApplication;
5 | import org.springframework.boot.autoconfigure.SpringBootApplication;
6 | import org.springframework.context.annotation.Bean;
7 | import org.springframework.context.annotation.Configuration;
8 | import org.springframework.jdbc.datasource.DriverManagerDataSource;
9 | import org.springframework.security.authentication.AuthenticationManager;
10 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
11 | import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
12 | import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
13 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
14 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
15 | import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
16 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
17 | import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
18 | import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
19 | import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
20 | import org.springframework.security.oauth2.provider.token.TokenEnhancer;
21 | import org.springframework.security.oauth2.provider.token.TokenStore;
22 | import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
23 | import org.springframework.web.bind.annotation.RequestMapping;
24 | import org.springframework.web.bind.annotation.RestController;
25 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
26 |
27 | import java.security.Principal;
28 |
29 | @SpringBootApplication
30 | @RestController
31 | public class Oauth2AuthorizationServerApplication extends WebMvcConfigurerAdapter {
32 |
33 | public static void main(String[] args) {
34 | SpringApplication.run(Oauth2AuthorizationServerApplication.class, args);
35 | }
36 |
37 | @Configuration
38 | @EnableResourceServer
39 | protected static class ResourceServer extends ResourceServerConfigurerAdapter {
40 |
41 | @Autowired
42 | private TokenStore tokenStore;
43 |
44 | @Override
45 | public void configure(ResourceServerSecurityConfigurer resources)
46 | throws Exception {
47 | resources.tokenStore(tokenStore);
48 | }
49 |
50 | @Override
51 | public void configure(HttpSecurity http) throws Exception {
52 | http.authorizeRequests()
53 | .antMatchers("/login")
54 | .permitAll();
55 | //http.authorizeRequests().anyRequest().authenticated();
56 | }
57 |
58 | }
59 |
60 | @Configuration
61 | @EnableAuthorizationServer
62 | protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {
63 | @Autowired
64 | private AuthenticationManager authenticationManager;
65 |
66 | @Autowired
67 | private DriverManagerDataSource dataSource;
68 |
69 | @Override
70 | public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
71 | endpoints.authorizationCodeServices(authorizationCodeServices())
72 | .authenticationManager(authenticationManager)
73 | .tokenStore(tokenStore())
74 | .tokenEnhancer(tokenEnhancer());
75 | }
76 |
77 | /**
78 | * Enhances the json response for the /token endpoint with the openid id_token param.
79 | *
80 | */
81 | @Bean
82 | public TokenEnhancer tokenEnhancer() {
83 | return new OpenApiTokenEnhancer();
84 | }
85 |
86 | /**
87 | * Stores the client oauth authorization codes and client access tokens in a jdbc datastore. This store is managed by oauth2.
88 | * @return
89 | */
90 | @Bean
91 | public JdbcTokenStore tokenStore() {
92 | return new JdbcTokenStore(dataSource);
93 | }
94 | @Bean
95 | protected AuthorizationCodeServices authorizationCodeServices() {
96 | return new JdbcAuthorizationCodeServices(dataSource);
97 | }
98 |
99 |
100 | @Override
101 | public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
102 | clients.jdbc(dataSource)
103 | .withClient("ING_BANK")
104 | .secret("ingbanksecret")
105 | .authorizedGrantTypes("authorization_code", "password")
106 | .scopes("openid");
107 | }
108 | }
109 |
110 | @RequestMapping("/user")
111 | public Principal user(Principal user) {
112 | return user;
113 | }
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/OpenApiTokenEnhancer.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial;
2 |
3 | import com.hascode.tutorial.auth.JwtAuthenticatedProfile;
4 | import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
5 | import org.springframework.security.oauth2.common.OAuth2AccessToken;
6 | import org.springframework.security.oauth2.provider.OAuth2Authentication;
7 | import org.springframework.security.oauth2.provider.token.TokenEnhancer;
8 |
9 | import java.util.HashMap;
10 | import java.util.Map;
11 | import java.util.UUID;
12 |
13 | public class OpenApiTokenEnhancer implements TokenEnhancer {
14 |
15 | @Override
16 | public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
17 | JwtAuthenticatedProfile user = (JwtAuthenticatedProfile) authentication.getPrincipal();
18 | final Map additionalInfo = new HashMap<>();
19 |
20 | additionalInfo.put("id_token", UUID.randomUUID().toString());
21 |
22 | ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
23 |
24 | return accessToken;
25 | }
26 |
27 | }
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/SecurityConfig.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial;
2 |
3 | import com.hascode.tutorial.auth.JwtAuthFilter;
4 | import com.hascode.tutorial.auth.JwtAuthenticationEntryPoint;
5 | import com.hascode.tutorial.auth.JwtAuthenticationProvider;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.context.annotation.Configuration;
8 | import org.springframework.http.HttpMethod;
9 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
10 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
11 | import org.springframework.security.config.annotation.web.builders.WebSecurity;
12 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
13 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
14 |
15 | @Configuration
16 | public class SecurityConfig extends WebSecurityConfigurerAdapter {
17 |
18 | @Autowired
19 | private JwtAuthFilter jwtAuthFilter;
20 |
21 | @Autowired
22 | private JwtAuthenticationProvider jwtAuthenticationProvider;
23 |
24 | @Autowired
25 | private JwtAuthenticationEntryPoint jwtAuthEndPoint;
26 |
27 | @Override
28 | public void configure(AuthenticationManagerBuilder auth) throws Exception {
29 | auth.authenticationProvider(jwtAuthenticationProvider);
30 | }
31 |
32 | @Override
33 | protected void configure(HttpSecurity http) throws Exception {
34 | http.csrf().ignoringAntMatchers("/login");
35 |
36 | http.authorizeRequests()
37 | .antMatchers("/login")
38 | .permitAll()
39 | .antMatchers("/oauth/token")
40 | .authenticated()
41 | .antMatchers("/oauth/authorize")
42 | .hasAuthority("ROLE_USER")
43 | .and()
44 | .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
45 | .exceptionHandling()
46 | .authenticationEntryPoint(jwtAuthEndPoint);
47 | }
48 | }
49 |
50 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/auth/JwtAuthFilter.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial.auth;
2 |
3 | import org.springframework.security.core.context.SecurityContextHolder;
4 | import org.springframework.stereotype.Component;
5 | import org.springframework.web.filter.OncePerRequestFilter;
6 |
7 | import javax.servlet.FilterChain;
8 | import javax.servlet.ServletException;
9 | import javax.servlet.http.HttpServletRequest;
10 | import javax.servlet.http.HttpServletResponse;
11 | import java.io.IOException;
12 |
13 | @Component
14 | public class JwtAuthFilter extends OncePerRequestFilter {
15 |
16 | @Override
17 | protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain chain) throws ServletException, IOException {
18 | String authorization = httpServletRequest.getHeader("Authorization");
19 | if (authorization != null && !authorization.startsWith("Basic")) {
20 | JwtAuthToken token = new JwtAuthToken(authorization.replaceAll("Bearer ", ""));
21 | SecurityContextHolder.getContext().setAuthentication(token);
22 | }
23 | chain.doFilter(httpServletRequest, httpServletResponse);
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/auth/JwtAuthToken.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial.auth;
2 |
3 | import org.springframework.security.core.Authentication;
4 | import org.springframework.security.core.GrantedAuthority;
5 |
6 | import java.util.Collection;
7 |
8 | public class JwtAuthToken implements Authentication {
9 | private final String token;
10 |
11 | public JwtAuthToken(String token) {
12 | this.token = token;
13 | }
14 |
15 | @Override
16 | public Collection extends GrantedAuthority> getAuthorities() {
17 | return null;
18 | }
19 |
20 | @Override
21 | public Object getCredentials() {
22 | return token;
23 | }
24 |
25 | @Override
26 | public Object getDetails() {
27 | return null;
28 | }
29 |
30 | @Override
31 | public Object getPrincipal() {
32 | return null;
33 | }
34 |
35 | @Override
36 | public boolean isAuthenticated() {
37 | return false;
38 | }
39 |
40 | @Override
41 | public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
42 |
43 | }
44 |
45 | @Override
46 | public String getName() {
47 | return null;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/auth/JwtAuthenticatedProfile.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial.auth;
2 |
3 | import com.hascode.tutorial.domain.MinimalProfile;
4 | import org.springframework.security.core.Authentication;
5 | import org.springframework.security.core.GrantedAuthority;
6 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
7 |
8 | import java.util.Collection;
9 | import java.util.Collections;
10 |
11 | public class JwtAuthenticatedProfile implements Authentication {
12 |
13 | private final MinimalProfile minimalProfile;
14 |
15 | public JwtAuthenticatedProfile(MinimalProfile minimalProfile) {
16 | this.minimalProfile = minimalProfile;
17 | }
18 |
19 | @Override
20 | public Collection extends GrantedAuthority> getAuthorities() {
21 | return Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
22 | }
23 |
24 | @Override
25 | public Object getCredentials() {
26 | return null;
27 | }
28 |
29 | @Override
30 | public Object getDetails() {
31 | return null;
32 | }
33 |
34 | @Override
35 | public Object getPrincipal() {
36 | return this;
37 | }
38 |
39 | @Override
40 | public boolean isAuthenticated() {
41 | return true;
42 | }
43 |
44 | @Override
45 | public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
46 |
47 | }
48 |
49 | @Override
50 | public String getName() {
51 | return minimalProfile.getUsername();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/auth/JwtAuthenticationEntryPoint.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial.auth;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 | import org.springframework.http.MediaType;
5 | import org.springframework.security.core.AuthenticationException;
6 | import org.springframework.security.web.AuthenticationEntryPoint;
7 | import org.springframework.stereotype.Component;
8 |
9 | import javax.servlet.ServletException;
10 | import javax.servlet.http.HttpServletRequest;
11 | import javax.servlet.http.HttpServletResponse;
12 | import java.io.IOException;
13 | import java.util.Collections;
14 |
15 | import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
16 |
17 | @Component
18 | public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
19 | @Override
20 | public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e)
21 | throws IOException, ServletException {
22 | httpServletResponse.setStatus(SC_FORBIDDEN);
23 | httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
24 |
25 | String message;
26 | if(e.getCause() != null) {
27 | message = e.getCause().getMessage();
28 | } else {
29 | message = e.getMessage();
30 | }
31 | byte[] body = new ObjectMapper()
32 | .writeValueAsBytes(Collections.singletonMap("error", message));
33 | httpServletResponse.getOutputStream().write(body);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/auth/JwtAuthenticationProvider.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial.auth;
2 |
3 | import com.hascode.tutorial.domain.MinimalProfile;
4 | import com.hascode.tutorial.exceptions.JwtAuthenticationException;
5 | import com.hascode.tutorial.service.JwtService;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.security.authentication.AuthenticationProvider;
8 | import org.springframework.security.core.Authentication;
9 | import org.springframework.security.core.AuthenticationException;
10 | import org.springframework.stereotype.Component;
11 |
12 | import java.util.Optional;
13 |
14 | @Component
15 | public class JwtAuthenticationProvider implements AuthenticationProvider {
16 | private final JwtService jwtService;
17 |
18 | @SuppressWarnings("unused")
19 | public JwtAuthenticationProvider() {
20 | this(null);
21 | }
22 |
23 | @Autowired
24 | public JwtAuthenticationProvider(JwtService jwtService) {
25 | this.jwtService = jwtService;
26 | }
27 |
28 | @Override
29 | public Authentication authenticate(Authentication authentication) throws AuthenticationException {
30 | try {
31 | Optional possibleProfile = jwtService.verify((String) authentication.getCredentials());
32 | return new JwtAuthenticatedProfile(possibleProfile.get());
33 | } catch (Exception e) {
34 | throw new JwtAuthenticationException("Failed to verify token", e);
35 | }
36 | }
37 |
38 | @Override
39 | public boolean supports(Class> authentication) {
40 | return JwtAuthToken.class.equals(authentication);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/auth/LoginCredentials.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial.auth;
2 |
3 | import lombok.Data;
4 |
5 | @Data
6 | public class LoginCredentials {
7 | private String username;
8 | private String password;
9 | }
10 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/auth/SecretKeyProvider.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial.auth;
2 |
3 | import org.springframework.stereotype.Component;
4 |
5 | import java.io.IOException;
6 | import java.net.URISyntaxException;
7 | import java.nio.file.Files;
8 | import java.nio.file.Paths;
9 |
10 | @Component
11 | public class SecretKeyProvider {
12 | public byte[] getKey() throws URISyntaxException, IOException {
13 | return Files.readAllBytes(Paths.get(this.getClass().getResource("/jwt.key").toURI()));
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/domain/DetailedProfile.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial.domain;
2 |
3 | import lombok.Data;
4 |
5 | import java.io.Serializable;
6 |
7 | @Data
8 | public class DetailedProfile implements Serializable {
9 |
10 | private final Picture picture;
11 | private final Name name;
12 | private final String email;
13 | private final String username;
14 |
15 | public DetailedProfile(Profile profile) {
16 | name = profile.getName();
17 | email = profile.getEmail();
18 | picture = profile.getPicture();
19 | username = profile.getLogin().getUsername();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/domain/Login.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial.domain;
2 |
3 | import lombok.*;
4 |
5 | import java.io.Serializable;
6 |
7 | @Data
8 | public class Login implements Serializable {
9 | String username;
10 | String password;
11 | String salt;
12 | String md5;
13 | String sha1;
14 | String sha256;
15 | }
16 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/domain/MinimalProfile.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial.domain;
2 |
3 | import lombok.Data;
4 |
5 | import java.io.Serializable;
6 | import java.net.URL;
7 |
8 | @Data
9 | public class MinimalProfile implements Serializable {
10 | private final String username;
11 | private final Name name;
12 | private final URL thumbnail;
13 |
14 | public MinimalProfile(Profile profile) {
15 | name = profile.getName();
16 | username = profile.getLogin().getUsername();
17 | thumbnail = profile.getPicture().getThumbnail();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/domain/Name.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial.domain;
2 |
3 | import lombok.Data;
4 |
5 | import java.io.Serializable;
6 |
7 | @Data
8 | public class Name implements Serializable {
9 | private String title;
10 | private String first;
11 | private String last;
12 | }
13 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/domain/Picture.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial.domain;
2 |
3 | import lombok.Data;
4 |
5 | import java.io.Serializable;
6 | import java.net.URL;
7 |
8 | @Data
9 | public class Picture implements Serializable {
10 | private URL large;
11 | private URL medium;
12 | private URL thumbnail;
13 | }
14 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/domain/Profile.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial.domain;
2 |
3 |
4 | import lombok.Data;
5 |
6 | import java.io.Serializable;
7 |
8 | @Data
9 | public class Profile implements Serializable {
10 | private Name name;
11 | private String email;
12 | private Login login;
13 | private Picture picture;
14 | }
15 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/exceptions/FailedToLoginException.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial.exceptions;
2 |
3 | import static java.lang.String.format;
4 |
5 | public class FailedToLoginException extends RuntimeException {
6 | public FailedToLoginException(String username) {
7 | super(format("Failed to login with username %s", username));
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/exceptions/JwtAuthenticationException.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial.exceptions;
2 |
3 | import org.springframework.security.core.AuthenticationException;
4 |
5 | public class JwtAuthenticationException extends AuthenticationException {
6 | public JwtAuthenticationException(String msg, Throwable t) {
7 | super(msg, t);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/exceptions/ProfileNotFoundException.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial.exceptions;
2 |
3 | import static java.lang.String.format;
4 |
5 | public class ProfileNotFoundException extends RuntimeException {
6 | public ProfileNotFoundException(String username) {
7 | super(format("Profile with username %s does not exist", username));
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/service/JwtService.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial.service;
2 |
3 | import com.hascode.tutorial.auth.SecretKeyProvider;
4 | import com.hascode.tutorial.domain.MinimalProfile;
5 | import io.jsonwebtoken.Claims;
6 | import io.jsonwebtoken.Jws;
7 | import io.jsonwebtoken.Jwts;
8 | import io.jsonwebtoken.SignatureAlgorithm;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.stereotype.Component;
11 |
12 | import java.io.IOException;
13 | import java.net.URISyntaxException;
14 | import java.time.LocalDateTime;
15 | import java.util.Date;
16 | import java.util.Optional;
17 |
18 | import static java.time.ZoneOffset.UTC;
19 |
20 |
21 | @Component
22 | public class JwtService {
23 | private static final String ISSUER = "in.sdqali.jwt";
24 | public static final String USERNAME = "username";
25 | private final SecretKeyProvider secretKeyProvider;
26 | private final ProfileService profileService;
27 |
28 | @SuppressWarnings("unused")
29 | public JwtService() {
30 | this(null, null);
31 | }
32 |
33 | @Autowired
34 | public JwtService(SecretKeyProvider secretKeyProvider, ProfileService profileService) {
35 | this.secretKeyProvider = secretKeyProvider;
36 | this.profileService = profileService;
37 | }
38 |
39 | public String tokenFor(MinimalProfile minimalProfile) throws IOException, URISyntaxException {
40 | byte[] secretKey = secretKeyProvider.getKey();
41 | Date expiration = Date.from(LocalDateTime.now(UTC).plusHours(2).toInstant(UTC));
42 | return Jwts.builder()
43 | .setSubject(minimalProfile.getUsername())
44 | .setExpiration(expiration)
45 | .setIssuer(ISSUER)
46 | .signWith(SignatureAlgorithm.HS512, secretKey)
47 | .compact();
48 | }
49 |
50 | public Optional verify(String token) throws IOException, URISyntaxException {
51 | byte[] secretKey = secretKeyProvider.getKey();
52 | Jws claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
53 | return profileService.minimal(claims.getBody().getSubject().toString());
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/service/LoginService.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial.service;
2 |
3 | import com.hascode.tutorial.auth.LoginCredentials;
4 | import com.hascode.tutorial.domain.MinimalProfile;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.stereotype.Component;
7 |
8 | import java.util.Optional;
9 |
10 | @Component
11 | public class LoginService {
12 |
13 | private ProfileService profileService;
14 |
15 | @SuppressWarnings("unused")
16 | public LoginService() {
17 | this(null);
18 | }
19 |
20 | @Autowired
21 | public LoginService(ProfileService profileService) {
22 | this.profileService = profileService;
23 | }
24 |
25 | public Optional login(LoginCredentials credentials) {
26 | return profileService.get(credentials.getUsername())
27 | .filter(profile -> profile.getLogin().getPassword().equals(credentials.getPassword()))
28 | .map(profile -> new MinimalProfile(profile));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/service/ProfileService.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial.service;
2 |
3 | import com.fasterxml.jackson.core.type.TypeReference;
4 | import com.fasterxml.jackson.databind.ObjectMapper;
5 | import com.hascode.tutorial.domain.DetailedProfile;
6 | import com.hascode.tutorial.domain.MinimalProfile;
7 | import com.hascode.tutorial.domain.Profile;
8 | import org.springframework.stereotype.Component;
9 |
10 | import java.io.IOException;
11 | import java.net.URISyntaxException;
12 | import java.nio.file.Path;
13 | import java.nio.file.Paths;
14 | import java.util.List;
15 | import java.util.Optional;
16 |
17 | @Component
18 | public class ProfileService {
19 | private final List profiles;
20 |
21 | private final Path PROFILES_FILE = Paths.get(this.getClass().getResource("/profiles.json").toURI());
22 |
23 | public ProfileService() throws IOException, URISyntaxException {
24 | ObjectMapper objectMapper = new ObjectMapper();
25 | profiles = objectMapper.readValue(PROFILES_FILE.toFile(), new TypeReference>() {
26 | });
27 | }
28 |
29 | protected Optional get(String username) {
30 | return profiles.stream()
31 | .filter(profile -> profile.getLogin().getUsername().equals(username))
32 | .findFirst();
33 | }
34 |
35 | public Optional minimal(String username) {
36 | return get(username).map(profile -> new MinimalProfile(profile));
37 | }
38 |
39 | public Optional detailed(String username) {
40 | return get(username).map(profile -> new DetailedProfile(profile));
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/web/LoginController.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial.web;
2 |
3 | import com.hascode.tutorial.auth.LoginCredentials;
4 | import com.hascode.tutorial.domain.MinimalProfile;
5 | import com.hascode.tutorial.exceptions.FailedToLoginException;
6 | import com.hascode.tutorial.service.JwtService;
7 | import com.hascode.tutorial.service.LoginService;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.web.bind.annotation.RequestBody;
10 | import org.springframework.web.bind.annotation.RequestMapping;
11 | import org.springframework.web.bind.annotation.RestController;
12 |
13 | import javax.servlet.http.HttpServletResponse;
14 |
15 | import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
16 | import static org.springframework.web.bind.annotation.RequestMethod.POST;
17 |
18 | @RestController
19 | @RequestMapping(path = "/login")
20 | public class LoginController {
21 |
22 | private final LoginService loginService;
23 | private final JwtService jwtService;
24 |
25 | @SuppressWarnings("unused")
26 | public LoginController() {
27 | this(null, null);
28 | }
29 |
30 | @Autowired
31 | public LoginController(LoginService loginService, JwtService jwtService) {
32 | this.loginService = loginService;
33 | this.jwtService = jwtService;
34 | }
35 |
36 | @RequestMapping(path = "",
37 | method = POST,
38 | produces = APPLICATION_JSON_VALUE)
39 | public MinimalProfile login(@RequestBody LoginCredentials credentials,
40 | HttpServletResponse response) {
41 | return loginService.login(credentials)
42 | .map(minimalProfile -> {
43 | try {
44 | response.setHeader("Token", jwtService.tokenFor(minimalProfile));
45 | } catch (Exception e) {
46 | throw new RuntimeException(e);
47 | }
48 | return minimalProfile;
49 | })
50 | .orElseThrow(() -> new FailedToLoginException(credentials.getUsername()));
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/web/ProfileController.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial.web;
2 |
3 | import com.hascode.tutorial.domain.DetailedProfile;
4 | import com.hascode.tutorial.domain.MinimalProfile;
5 | import com.hascode.tutorial.exceptions.ProfileNotFoundException;
6 | import com.hascode.tutorial.service.ProfileService;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.web.bind.annotation.PathVariable;
9 | import org.springframework.web.bind.annotation.RequestMapping;
10 | import org.springframework.web.bind.annotation.RestController;
11 |
12 | import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
13 | import static org.springframework.web.bind.annotation.RequestMethod.GET;
14 |
15 | @RestController
16 | @RequestMapping(path = "/profile")
17 | public class ProfileController {
18 |
19 | private final ProfileService profileService;
20 |
21 | @SuppressWarnings("unused")
22 | public ProfileController() {
23 | this(null);
24 | }
25 |
26 | @Autowired
27 | public ProfileController(ProfileService profileService) {
28 | this.profileService = profileService;
29 | }
30 |
31 | @RequestMapping(path = "/{username}",
32 | method = GET,
33 | produces = APPLICATION_JSON_VALUE)
34 | public MinimalProfile minimal(@PathVariable String username) {
35 | return profileService.minimal(username)
36 | .orElseThrow(() -> new ProfileNotFoundException(username));
37 | }
38 |
39 | @RequestMapping(path = "/details/{username}",
40 | method = GET,
41 | produces = APPLICATION_JSON_VALUE)
42 | public DetailedProfile details(@PathVariable String username) {
43 | return profileService.detailed(username)
44 | .orElseThrow(() -> new ProfileNotFoundException(username));
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/identity-server/src/main/java/com/hascode/tutorial/web/advices/GlobalExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package com.hascode.tutorial.web.advices;
2 |
3 | import com.hascode.tutorial.exceptions.FailedToLoginException;
4 | import com.hascode.tutorial.exceptions.ProfileNotFoundException;
5 | import org.springframework.web.bind.annotation.ControllerAdvice;
6 | import org.springframework.web.bind.annotation.ExceptionHandler;
7 | import org.springframework.web.bind.annotation.ResponseStatus;
8 |
9 | import java.security.SignatureException;
10 |
11 | import static org.springframework.http.HttpStatus.*;
12 |
13 | @ControllerAdvice
14 | public class GlobalExceptionHandler {
15 | @ResponseStatus(NOT_FOUND)
16 | @ExceptionHandler(ProfileNotFoundException.class)
17 | public void profileNotFound() {
18 | }
19 |
20 | @ResponseStatus(UNAUTHORIZED)
21 | @ExceptionHandler(FailedToLoginException.class)
22 | public void failedToLogin() {
23 | }
24 |
25 | @ResponseStatus(FORBIDDEN)
26 | @ExceptionHandler(SignatureException.class)
27 | public void failedToVerify() {
28 | System.out.println("");
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/identity-server/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | security.basic.enabled=false
2 | server.port=9000
3 | server.contextPath=/
4 | log4j.category.org.springframework=ALL
5 | logging.level.org.springframework.web=DEBUG
--------------------------------------------------------------------------------
/identity-server/src/main/resources/jwt.key:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nilathj/spring-oauth2-jwt-jdbc/60653cb3689b6f44d78e7c11e4de7bce85a363e9/identity-server/src/main/resources/jwt.key
--------------------------------------------------------------------------------
/identity-server/src/main/resources/schema.sql:
--------------------------------------------------------------------------------
1 | drop table if exists oauth_client_details;
2 | create table oauth_client_details (
3 | client_id VARCHAR(255) PRIMARY KEY,
4 | resource_ids VARCHAR(255),
5 | client_secret VARCHAR(255),
6 | scope VARCHAR(255),
7 | authorized_grant_types VARCHAR(255),
8 | web_server_redirect_uri VARCHAR(255),
9 | authorities VARCHAR(255),
10 | access_token_validity INTEGER,
11 | refresh_token_validity INTEGER,
12 | additional_information VARCHAR(4096),
13 | autoapprove VARCHAR(255)
14 | );
15 | drop table if exists oauth_client_token;
16 | create table oauth_client_token (
17 | token_id VARCHAR(255),
18 | token LONG VARBINARY,
19 | authentication_id VARCHAR(255) PRIMARY KEY,
20 | user_name VARCHAR(255),
21 | client_id VARCHAR(255)
22 | );
23 | drop table if exists oauth_access_token;
24 | create table oauth_access_token (
25 | token_id VARCHAR(255),
26 | token LONG VARBINARY,
27 | authentication_id VARCHAR(255) PRIMARY KEY,
28 | user_name VARCHAR(255),
29 | client_id VARCHAR(255),
30 | authentication LONG VARBINARY,
31 | refresh_token VARCHAR(255)
32 | );
33 | drop table if exists oauth_refresh_token;
34 | create table oauth_refresh_token (
35 | token_id VARCHAR(255),
36 | token LONG VARBINARY,
37 | authentication LONG VARBINARY
38 | );
39 | drop table if exists oauth_code;
40 | create table oauth_code (
41 | code VARCHAR(255), authentication LONG VARBINARY
42 | );
43 | drop table if exists oauth_approvals;
44 | create table oauth_approvals (
45 | userId VARCHAR(255),
46 | clientId VARCHAR(255),
47 | scope VARCHAR(255),
48 | status VARCHAR(10),
49 | expiresAt TIMESTAMP,
50 | lastModifiedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
51 | );
52 | drop table if exists ClientDetails;
53 | create table ClientDetails (
54 | appId VARCHAR(255) PRIMARY KEY,
55 | resourceIds VARCHAR(255),
56 | appSecret VARCHAR(255),
57 | scope VARCHAR(255),
58 | grantTypes VARCHAR(255),
59 | redirectUrl VARCHAR(255),
60 | authorities VARCHAR(255),
61 | access_token_validity INTEGER,
62 | refresh_token_validity INTEGER,
63 | additionalInformation VARCHAR(4096),
64 | autoApproveScopes VARCHAR(255)
65 | );
--------------------------------------------------------------------------------