├── src ├── main │ ├── resources │ │ ├── logback.xml │ │ └── application.properties │ ├── java │ │ ├── com │ │ │ └── jdriven │ │ │ │ └── stateless │ │ │ │ └── security │ │ │ │ ├── UserRepository.java │ │ │ │ ├── SocialUserService.java │ │ │ │ ├── UserRole.java │ │ │ │ ├── FacebookController.java │ │ │ │ ├── UserAuthenticationUserIdSource.java │ │ │ │ ├── StatelessAuthentication.java │ │ │ │ ├── UserAuthentication.java │ │ │ │ ├── UserAuthority.java │ │ │ │ ├── SocialAuthenticationSuccessHandler.java │ │ │ │ ├── StatelessAuthenticationFilter.java │ │ │ │ ├── UserService.java │ │ │ │ ├── TokenAuthenticationService.java │ │ │ │ ├── AutoSignUpHandler.java │ │ │ │ ├── StatelessSocialConfig.java │ │ │ │ ├── UserController.java │ │ │ │ ├── SimpleUsersConnectionRepository.java │ │ │ │ ├── TokenHandler.java │ │ │ │ ├── StatelessAuthenticationSecurityConfig.java │ │ │ │ └── User.java │ │ └── org │ │ │ └── springframework │ │ │ └── social │ │ │ └── connect │ │ │ └── mem │ │ │ └── TemporaryConnectionRepository.java │ └── webapp │ │ ├── index.html │ │ └── resources │ │ └── js │ │ ├── controllers.js │ │ └── angular-cookies.js └── test │ └── java │ └── com │ └── jdriven │ └── stateless │ └── security │ └── TokenHandlerTest.java ├── README.md ├── LICENSE └── pom.xml /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/java/com/jdriven/stateless/security/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.jdriven.stateless.security; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface UserRepository extends JpaRepository { 6 | 7 | User findByUsername(String username); 8 | 9 | User findById(Long id); 10 | 11 | User findByProviderIdAndProviderUserId(String providerId, String providerUserId); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/connect/mem/TemporaryConnectionRepository.java: -------------------------------------------------------------------------------- 1 | package org.springframework.social.connect.mem; 2 | 3 | import org.springframework.social.connect.ConnectionFactoryLocator; 4 | 5 | /** 6 | * A short-lived (per request) ConnectionRepository for a single user 7 | */ 8 | public class TemporaryConnectionRepository extends InMemoryConnectionRepository { 9 | public TemporaryConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) { 10 | super(connectionFactoryLocator); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/jdriven/stateless/security/SocialUserService.java: -------------------------------------------------------------------------------- 1 | package com.jdriven.stateless.security; 2 | 3 | import org.springframework.security.core.userdetails.UserDetailsService; 4 | import org.springframework.social.connect.ConnectionKey; 5 | import org.springframework.social.security.SocialUserDetailsService; 6 | 7 | public interface SocialUserService extends SocialUserDetailsService, UserDetailsService { 8 | 9 | User loadUserByConnectionKey(ConnectionKey connectionKey); 10 | User loadUserByUserId(String userId); 11 | User loadUserByUsername(String username); 12 | void updateUserDetails(User user); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | security.basic.enabled=false 2 | server.tomcat.uri-encoding=UTF-8 3 | token.secret=9SyECk96oDsTmXfogIieDI0cD/8FpnojlYSUJT5U9I/FGVmBz5oskmjOR8cbXTvoPjX+Pq/T/b1PqpHX0lYm0oCBjXWICA== 4 | 5 | facebook.appKey=771895819532005 6 | facebook.appSecret=f9b4089daf0607fa424033f77b887a8b 7 | facebook.appNamespace=statelesssocial 8 | 9 | spring.jpa.hibernate.ddl-auto=update 10 | spring.jpa.showSql=true 11 | 12 | # run on local postgresql (change runtime dep in pom/build file as well) 13 | #spring.datasource.url=jdbc:postgresql://localhost:5432/statelesssocial 14 | #spring.datasource.username=statelesssocial 15 | #spring.datasource.password=statelesssocial 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | boot-stateless-social 2 | =================== 3 | Example project integrating https://github.com/Robbert1/boot-stateless-auth with OAuth 2 based social login with facebook. 4 | 5 | Facebook expects the app to run under http://socialshowcase.com(:8080). 6 | Therefore you need to add the following line to your hosts file for testing it locally: 7 | 127.0.0.1  socialshowcase.com 8 | 9 | The build files and application.properties include commented out configuration for postgresql, mostly for testing behavior across server reboots. 10 | 11 | Needs Gradle 2 or maven 3 and JDK 7 12 | 13 | build with `gradle build` 14 | run with `gradle run` 15 | 16 | or go with `mvn spring-boot:run` 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/main/java/com/jdriven/stateless/security/UserRole.java: -------------------------------------------------------------------------------- 1 | package com.jdriven.stateless.security; 2 | 3 | public enum UserRole { 4 | USER, ADMIN; 5 | 6 | public UserAuthority asAuthorityFor(final User user) { 7 | final UserAuthority authority = new UserAuthority(); 8 | authority.setAuthority("ROLE_" + toString()); 9 | authority.setUser(user); 10 | return authority; 11 | } 12 | 13 | public static UserRole valueOf(final UserAuthority authority) { 14 | switch (authority.getAuthority()) { 15 | case "ROLE_USER": 16 | return USER; 17 | case "ROLE_ADMIN": 18 | return ADMIN; 19 | } 20 | throw new IllegalArgumentException("No role defined for authority: " + authority.getAuthority()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/jdriven/stateless/security/FacebookController.java: -------------------------------------------------------------------------------- 1 | package com.jdriven.stateless.security; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.social.facebook.api.Facebook; 5 | import org.springframework.social.facebook.api.FacebookProfile; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestMethod; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | @RestController 11 | public class FacebookController { 12 | 13 | @Autowired 14 | Facebook facebook; 15 | 16 | @RequestMapping(value = "/api/facebook/details", method = RequestMethod.GET) 17 | public FacebookProfile getSocialDetails() { 18 | return facebook.userOperations().getUserProfile(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/jdriven/stateless/security/UserAuthenticationUserIdSource.java: -------------------------------------------------------------------------------- 1 | package com.jdriven.stateless.security; 2 | 3 | import org.springframework.security.core.Authentication; 4 | import org.springframework.security.core.context.SecurityContextHolder; 5 | import org.springframework.social.UserIdSource; 6 | import org.springframework.social.security.SocialAuthenticationToken; 7 | 8 | public class UserAuthenticationUserIdSource implements UserIdSource { 9 | 10 | @Override 11 | public String getUserId() { 12 | final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); 13 | User user = null; 14 | if (authentication instanceof UserAuthentication) { 15 | user = (User) authentication.getPrincipal(); 16 | } 17 | 18 | if (user == null) { 19 | throw new IllegalStateException("Unable to get a ConnectionRepository: no user signed in"); 20 | } 21 | return user.getUserId(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Stateless Authentication Example 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 |
14 | Logged in as {{username}}

15 | Token content:
{{token | json}}
16 |
17 |
18 | 19 |
{{socialDetails | json}}
20 |
21 | 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 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 | 23 | -------------------------------------------------------------------------------- /src/main/java/com/jdriven/stateless/security/StatelessAuthentication.java: -------------------------------------------------------------------------------- 1 | package com.jdriven.stateless.security; 2 | 3 | import javax.servlet.Filter; 4 | 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 7 | import org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.ComponentScan; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.web.filter.CharacterEncodingFilter; 12 | 13 | @EnableAutoConfiguration(exclude = { SocialWebAutoConfiguration.class }) 14 | @Configuration 15 | @ComponentScan 16 | public class StatelessAuthentication { 17 | 18 | public static void main(String[] args) { 19 | SpringApplication.run(StatelessAuthentication.class, args); 20 | } 21 | 22 | @Bean 23 | public Filter characterEncodingFilter() { 24 | CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter(); 25 | characterEncodingFilter.setEncoding("UTF-8"); 26 | characterEncodingFilter.setForceEncoding(true); 27 | return characterEncodingFilter; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/jdriven/stateless/security/UserAuthentication.java: -------------------------------------------------------------------------------- 1 | package com.jdriven.stateless.security; 2 | 3 | import java.util.Collection; 4 | 5 | import org.springframework.security.core.Authentication; 6 | import org.springframework.security.core.GrantedAuthority; 7 | 8 | public class UserAuthentication implements Authentication { 9 | 10 | private final User user; 11 | private boolean authenticated = true; 12 | 13 | public UserAuthentication(User user) { 14 | this.user = user; 15 | } 16 | 17 | @Override 18 | public String getName() { 19 | return user.getUsername(); 20 | } 21 | 22 | @Override 23 | public Collection getAuthorities() { 24 | return user.getAuthorities(); 25 | } 26 | 27 | @Override 28 | public Object getCredentials() { 29 | return null; 30 | } 31 | 32 | @Override 33 | public User getDetails() { 34 | return user; 35 | } 36 | 37 | @Override 38 | public Object getPrincipal() { 39 | return user; 40 | } 41 | 42 | @Override 43 | public boolean isAuthenticated() { 44 | return authenticated; 45 | } 46 | 47 | @Override 48 | public void setAuthenticated(boolean authenticated) { 49 | this.authenticated = authenticated; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/jdriven/stateless/security/UserAuthority.java: -------------------------------------------------------------------------------- 1 | package com.jdriven.stateless.security; 2 | 3 | import javax.persistence.*; 4 | import javax.validation.constraints.NotNull; 5 | 6 | import org.springframework.security.core.GrantedAuthority; 7 | 8 | import com.fasterxml.jackson.annotation.JsonIgnore; 9 | 10 | @Entity 11 | @IdClass(UserAuthority.class) 12 | public class UserAuthority implements GrantedAuthority { 13 | 14 | @NotNull 15 | @ManyToOne(fetch = FetchType.LAZY) 16 | @JsonIgnore 17 | @Id 18 | private User user; 19 | 20 | @NotNull 21 | @Id 22 | private String authority; 23 | 24 | public User getUser() { 25 | return user; 26 | } 27 | 28 | public void setUser(User user) { 29 | this.user = user; 30 | } 31 | 32 | @Override 33 | public String getAuthority() { 34 | return authority; 35 | } 36 | 37 | public void setAuthority(String authority) { 38 | this.authority = authority; 39 | } 40 | 41 | @Override 42 | public boolean equals(Object obj) { 43 | if (!(obj instanceof UserAuthority)) 44 | return false; 45 | 46 | UserAuthority ua = (UserAuthority) obj; 47 | return ua.getAuthority() == this.getAuthority() || ua.getAuthority().equals(this.getAuthority()); 48 | } 49 | 50 | @Override 51 | public int hashCode() { 52 | return getAuthority() == null ? 0 : getAuthority().hashCode(); 53 | } 54 | 55 | @Override 56 | public String toString() { 57 | return getClass().getSimpleName() + ": " + getAuthority(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/jdriven/stateless/security/SocialAuthenticationSuccessHandler.java: -------------------------------------------------------------------------------- 1 | package com.jdriven.stateless.security; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.ServletException; 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.security.core.Authentication; 11 | import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; 12 | import org.springframework.stereotype.Component; 13 | 14 | @Component 15 | public class SocialAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { 16 | 17 | @Autowired 18 | private TokenAuthenticationService tokenAuthenticationService; 19 | 20 | @Autowired 21 | private SocialUserService userService; 22 | 23 | @Override 24 | public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, 25 | Authentication authentication) throws ServletException, IOException { 26 | 27 | // Lookup the complete User object from the database and create an Authentication for it 28 | final User authenticatedUser = userService.loadUserByUsername(authentication.getName()); 29 | 30 | // Add UserAuthentication to the response 31 | final UserAuthentication userAuthentication = new UserAuthentication(authenticatedUser); 32 | tokenAuthenticationService.addAuthentication(response, userAuthentication); 33 | super.onAuthenticationSuccess(request, response, authentication); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/jdriven/stateless/security/StatelessAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package com.jdriven.stateless.security; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.FilterChain; 6 | import javax.servlet.ServletException; 7 | import javax.servlet.ServletRequest; 8 | import javax.servlet.ServletResponse; 9 | import javax.servlet.http.HttpServletRequest; 10 | 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.security.core.Authentication; 13 | import org.springframework.security.core.context.SecurityContextHolder; 14 | import org.springframework.stereotype.Component; 15 | import org.springframework.web.filter.GenericFilterBean; 16 | 17 | @Component 18 | public class StatelessAuthenticationFilter extends GenericFilterBean { 19 | 20 | @Autowired 21 | private TokenAuthenticationService tokenAuthenticationService; 22 | 23 | @Override 24 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, 25 | ServletException { 26 | 27 | setAuthenticationFromHeader((HttpServletRequest) request); 28 | 29 | chain.doFilter(request, response); 30 | } 31 | 32 | private void setAuthenticationFromHeader(HttpServletRequest request) { 33 | final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); 34 | if (!(authentication instanceof UserAuthentication)) { 35 | final UserAuthentication userAuthentication = tokenAuthenticationService.getAuthentication(request); 36 | if (userAuthentication != null) { 37 | SecurityContextHolder.getContext().setAuthentication(userAuthentication); 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/java/com/jdriven/stateless/security/UserService.java: -------------------------------------------------------------------------------- 1 | package com.jdriven.stateless.security; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.security.authentication.AccountStatusUserDetailsChecker; 5 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 6 | import org.springframework.social.connect.ConnectionKey; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | @Service 11 | @Transactional 12 | public class UserService implements SocialUserService { 13 | 14 | @Autowired 15 | private UserRepository userRepo; 16 | 17 | private final AccountStatusUserDetailsChecker detailsChecker = new AccountStatusUserDetailsChecker(); 18 | 19 | @Override 20 | @Transactional(readOnly = true) 21 | public User loadUserByUserId(String userId) { 22 | final User user = userRepo.findById(Long.valueOf(userId)); 23 | return checkUser(user); 24 | } 25 | 26 | @Override 27 | @Transactional(readOnly = true) 28 | public User loadUserByUsername(String username) { 29 | final User user = userRepo.findByUsername(username); 30 | return checkUser(user); 31 | } 32 | 33 | @Override 34 | @Transactional(readOnly = true) 35 | public User loadUserByConnectionKey(ConnectionKey connectionKey) { 36 | final User user = userRepo.findByProviderIdAndProviderUserId(connectionKey.getProviderId(), connectionKey.getProviderUserId()); 37 | return checkUser(user); 38 | } 39 | 40 | @Override 41 | public void updateUserDetails(User user) { 42 | userRepo.save(user); 43 | } 44 | 45 | private User checkUser(User user) { 46 | if (user == null) { 47 | throw new UsernameNotFoundException("user not found"); 48 | } 49 | detailsChecker.check(user); 50 | return user; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/webapp/resources/js/controllers.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('statelessApp', ['ngCookies']).factory('TokenStorage', function() { 2 | var storageKey = 'auth_token'; 3 | return { 4 | store : function(token) { 5 | return localStorage.setItem(storageKey, token); 6 | }, 7 | retrieve : function() { 8 | return localStorage.getItem(storageKey); 9 | }, 10 | clear : function() { 11 | return localStorage.removeItem(storageKey); 12 | } 13 | }; 14 | }).factory('TokenAuthInterceptor', function($q, $rootScope, TokenStorage) { 15 | return { 16 | request: function(config) { 17 | var authToken = TokenStorage.retrieve(); 18 | if (authToken) { 19 | config.headers['X-AUTH-TOKEN'] = authToken; 20 | } 21 | return config; 22 | }, 23 | responseError: function(error) { 24 | if (error.status === 401 || error.status === 403) { 25 | TokenStorage.clear(); 26 | $rootScope.authenticated = false; 27 | } 28 | return $q.reject(error); 29 | } 30 | }; 31 | }).config(function($httpProvider) { 32 | $httpProvider.interceptors.push('TokenAuthInterceptor'); 33 | }); 34 | 35 | app.controller('AuthCtrl', function ($scope, $rootScope, $http, $cookies, TokenStorage) { 36 | $rootScope.authenticated = false; 37 | $scope.token; // For display purposes only 38 | 39 | $scope.init = function () { 40 | var authCookie = $cookies['AUTH-TOKEN']; 41 | if (authCookie) { 42 | TokenStorage.store(authCookie); 43 | delete $cookies['AUTH-TOKEN']; 44 | } 45 | $http.get('/api/user/current').success(function (user) { 46 | if (user.username) { 47 | $rootScope.authenticated = true; 48 | $scope.username = user.username; 49 | 50 | // For display purposes only 51 | $scope.token = JSON.parse(atob(TokenStorage.retrieve().split('.')[0])); 52 | } 53 | }); 54 | }; 55 | 56 | $scope.logout = function () { 57 | // Just clear the local storage 58 | TokenStorage.clear(); 59 | $rootScope.authenticated = false; 60 | }; 61 | 62 | $scope.getSocialDetails = function() { 63 | $http.get('/api/facebook/details').success(function (socialDetails) { 64 | $scope.socialDetails = socialDetails; 65 | }); 66 | }; 67 | }); -------------------------------------------------------------------------------- /src/test/java/com/jdriven/stateless/security/TokenHandlerTest.java: -------------------------------------------------------------------------------- 1 | package com.jdriven.stateless.security; 2 | 3 | import static javax.xml.bind.DatatypeConverter.printBase64Binary; 4 | import static org.junit.Assert.*; 5 | 6 | import java.security.SecureRandom; 7 | import java.util.Date; 8 | 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | 12 | import com.fasterxml.jackson.core.JsonProcessingException; 13 | import com.fasterxml.jackson.databind.ObjectMapper; 14 | 15 | public class TokenHandlerTest { 16 | 17 | private TokenHandler tokenHandler; 18 | 19 | @Before 20 | public void init() { 21 | byte[] secret = new byte[70]; 22 | new SecureRandom().nextBytes(secret); 23 | tokenHandler = new TokenHandler(secret); 24 | } 25 | 26 | @Test 27 | public void testRoundTrip_ProperData() { 28 | final User user = robbert(); 29 | user.grantRole(UserRole.ADMIN); 30 | 31 | final User parsedUser = tokenHandler.parseUserFromToken(tokenHandler.createTokenForUser(user)); 32 | 33 | assertEquals(user.getUsername(), parsedUser.getUsername()); 34 | assertTrue(parsedUser.hasRole(UserRole.ADMIN)); 35 | } 36 | 37 | @Test 38 | public void testCreateToken_SeparatorCharInUsername() { 39 | final User user = robbert(); 40 | 41 | final User parsedUser = tokenHandler.parseUserFromToken(tokenHandler.createTokenForUser(user)); 42 | 43 | assertEquals(user.getUsername(), parsedUser.getUsername()); 44 | } 45 | 46 | @Test 47 | public void testParseInvalidTokens_NoParseExceptions() throws JsonProcessingException { 48 | final String unsignedToken = printBase64Binary(new ObjectMapper().writeValueAsBytes(robbert())); 49 | 50 | testForNullResult(""); 51 | testForNullResult(unsignedToken); 52 | testForNullResult(unsignedToken + "."); 53 | testForNullResult(unsignedToken + "." + unsignedToken); 54 | } 55 | 56 | private void testForNullResult(final String token) { 57 | final User result = tokenHandler.parseUserFromToken(token); 58 | assertNull(result); 59 | } 60 | 61 | private User robbert() { 62 | final User robbert = new User(); 63 | robbert.setUsername("Robbert"); 64 | robbert.setId(123456L); 65 | robbert.setExpires(new Date(new Date().getTime() + 10000000).getTime()); 66 | return robbert; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/jdriven/stateless/security/TokenAuthenticationService.java: -------------------------------------------------------------------------------- 1 | package com.jdriven.stateless.security; 2 | 3 | import javax.servlet.http.Cookie; 4 | import javax.servlet.http.HttpServletRequest; 5 | import javax.servlet.http.HttpServletResponse; 6 | import javax.xml.bind.DatatypeConverter; 7 | 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.stereotype.Service; 11 | 12 | @Service 13 | public class TokenAuthenticationService { 14 | 15 | private static final String AUTH_HEADER_NAME = "X-AUTH-TOKEN"; 16 | private static final String AUTH_COOKIE_NAME = "AUTH-TOKEN"; 17 | private static final long TEN_DAYS = 1000 * 60 * 60 * 24 * 10; 18 | 19 | private final TokenHandler tokenHandler; 20 | 21 | @Autowired 22 | public TokenAuthenticationService(@Value("${token.secret}") String secret) { 23 | tokenHandler = new TokenHandler(DatatypeConverter.parseBase64Binary(secret)); 24 | } 25 | 26 | public void addAuthentication(HttpServletResponse response, UserAuthentication authentication) { 27 | final User user = authentication.getDetails(); 28 | user.setExpires(System.currentTimeMillis() + TEN_DAYS); 29 | final String token = tokenHandler.createTokenForUser(user); 30 | 31 | // Put the token into a cookie because the client can't capture response 32 | // headers of redirects / full page reloads. 33 | // (Its reloaded as a result of this response triggering a redirect back to "/") 34 | response.addCookie(createCookieForToken(token)); 35 | } 36 | 37 | public UserAuthentication getAuthentication(HttpServletRequest request) { 38 | // to prevent CSRF attacks we still only allow authentication using a custom HTTP header 39 | // (it is up to the client to read our previously set cookie and put it in the header) 40 | final String token = request.getHeader(AUTH_HEADER_NAME); 41 | if (token != null) { 42 | final User user = tokenHandler.parseUserFromToken(token); 43 | if (user != null) { 44 | return new UserAuthentication(user); 45 | } 46 | } 47 | return null; 48 | } 49 | 50 | private Cookie createCookieForToken(String token) { 51 | final Cookie authCookie = new Cookie(AUTH_COOKIE_NAME, token); 52 | authCookie.setPath("/"); 53 | return authCookie; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/jdriven/stateless/security/AutoSignUpHandler.java: -------------------------------------------------------------------------------- 1 | package com.jdriven.stateless.security; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.social.connect.Connection; 5 | import org.springframework.social.connect.ConnectionSignUp; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.transaction.annotation.Transactional; 8 | 9 | @Component 10 | public class AutoSignUpHandler implements ConnectionSignUp { 11 | 12 | @Autowired 13 | UserRepository userRepository; 14 | 15 | private volatile long userCount; 16 | 17 | @Override 18 | @Transactional 19 | public String execute(final Connection connection) { 20 | //add new users to the db with its default roles for later use in SocialAuthenticationSuccessHandler 21 | final User user = new User(); 22 | user.setUsername(generateUniqueUserName(connection.fetchUserProfile().getFirstName())); 23 | user.setProviderId(connection.getKey().getProviderId()); 24 | user.setProviderUserId(connection.getKey().getProviderUserId()); 25 | user.setAccessToken(connection.createData().getAccessToken()); 26 | grantRoles(user); 27 | userRepository.save(user); 28 | return user.getUserId(); 29 | } 30 | 31 | private void grantRoles(final User user) { 32 | user.grantRole(UserRole.USER); 33 | 34 | //grant admin rights to the first user 35 | if (userCount == 0) { 36 | userCount = userRepository.count(); 37 | if (userCount == 0) { 38 | user.grantRole(UserRole.ADMIN); 39 | } 40 | } 41 | } 42 | 43 | private String generateUniqueUserName(final String firstName) { 44 | String username = getUsernameFromFirstName(firstName); 45 | String option = username; 46 | for (int i = 0; userRepository.findByUsername(option) != null; i++) { 47 | option = username + i; 48 | } 49 | return option; 50 | } 51 | 52 | private String getUsernameFromFirstName(final String userId) { 53 | final int max = 25; 54 | int index = userId.indexOf(' '); 55 | index = index == -1 || index > max ? userId.length() : index; 56 | index = index > max ? max : index; 57 | return userId.substring(0, index); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/jdriven/stateless/security/StatelessSocialConfig.java: -------------------------------------------------------------------------------- 1 | package com.jdriven.stateless.security; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Scope; 7 | import org.springframework.context.annotation.ScopedProxyMode; 8 | import org.springframework.core.env.Environment; 9 | import org.springframework.social.UserIdSource; 10 | import org.springframework.social.config.annotation.ConnectionFactoryConfigurer; 11 | import org.springframework.social.config.annotation.EnableSocial; 12 | import org.springframework.social.config.annotation.SocialConfigurerAdapter; 13 | import org.springframework.social.connect.*; 14 | import org.springframework.social.facebook.api.Facebook; 15 | import org.springframework.social.facebook.connect.FacebookConnectionFactory; 16 | 17 | @Configuration 18 | @EnableSocial 19 | public class StatelessSocialConfig extends SocialConfigurerAdapter { 20 | 21 | @Autowired 22 | private ConnectionSignUp autoSignUpHandler; 23 | 24 | @Autowired 25 | private SocialUserService userService; 26 | 27 | @Override 28 | public void addConnectionFactories(ConnectionFactoryConfigurer cfConfig, Environment env) { 29 | cfConfig.addConnectionFactory(new FacebookConnectionFactory( 30 | env.getProperty("facebook.appKey"), 31 | env.getProperty("facebook.appSecret"))); 32 | } 33 | 34 | @Override 35 | public UserIdSource getUserIdSource() { 36 | // retrieve the UserId from the UserAuthentication in the security context 37 | return new UserAuthenticationUserIdSource(); 38 | } 39 | 40 | @Override 41 | public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) { 42 | SimpleUsersConnectionRepository usersConnectionRepository = 43 | new SimpleUsersConnectionRepository(userService, connectionFactoryLocator); 44 | 45 | // if no local user record exists yet for a facebook's user id 46 | // automatically create a User and add it to the database 47 | usersConnectionRepository.setConnectionSignUp(autoSignUpHandler); 48 | 49 | return usersConnectionRepository; 50 | } 51 | 52 | @Bean 53 | @Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES) 54 | public Facebook facebook(ConnectionRepository repository) { 55 | Connection connection = repository.findPrimaryConnection(Facebook.class); 56 | return connection != null ? connection.getApi() : null; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/jdriven/stateless/security/UserController.java: -------------------------------------------------------------------------------- 1 | package com.jdriven.stateless.security; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.security.core.Authentication; 9 | import org.springframework.security.core.context.SecurityContextHolder; 10 | import org.springframework.social.connect.Connection; 11 | import org.springframework.social.connect.ConnectionRepository; 12 | import org.springframework.social.connect.UsersConnectionRepository; 13 | import org.springframework.social.facebook.api.Facebook; 14 | import org.springframework.social.facebook.api.FacebookProfile; 15 | import org.springframework.web.bind.annotation.PathVariable; 16 | import org.springframework.web.bind.annotation.RequestMapping; 17 | import org.springframework.web.bind.annotation.RequestMethod; 18 | import org.springframework.web.bind.annotation.RestController; 19 | 20 | @RestController 21 | public class UserController { 22 | 23 | @Autowired 24 | UserRepository userRepository; 25 | 26 | @RequestMapping(value = "/api/user/current", method = RequestMethod.GET) 27 | public User getCurrent() { 28 | final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); 29 | if (authentication instanceof UserAuthentication) { 30 | return ((UserAuthentication) authentication).getDetails(); 31 | } 32 | return new User(); //anonymous user support 33 | } 34 | 35 | @RequestMapping(value = "/admin/api/user/{user}/grant/role/{role}", method = RequestMethod.POST) 36 | public ResponseEntity grantRole(@PathVariable User user, @PathVariable UserRole role) { 37 | if (user == null) { 38 | return new ResponseEntity<>("invalid user id", HttpStatus.UNPROCESSABLE_ENTITY); 39 | } 40 | 41 | user.grantRole(role); 42 | userRepository.saveAndFlush(user); 43 | return new ResponseEntity<>("role granted", HttpStatus.OK); 44 | } 45 | 46 | @RequestMapping(value = "/admin/api/user/{user}/revoke/role/{role}", method = RequestMethod.POST) 47 | public ResponseEntity revokeRole(@PathVariable User user, @PathVariable UserRole role) { 48 | if (user == null) { 49 | return new ResponseEntity<>("invalid user id", HttpStatus.UNPROCESSABLE_ENTITY); 50 | } 51 | 52 | user.revokeRole(role); 53 | userRepository.saveAndFlush(user); 54 | return new ResponseEntity<>("role revoked", HttpStatus.OK); 55 | } 56 | 57 | @RequestMapping(value = "/admin/api/user", method = RequestMethod.GET) 58 | public List list() { 59 | return userRepository.findAll(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/jdriven/stateless/security/SimpleUsersConnectionRepository.java: -------------------------------------------------------------------------------- 1 | package com.jdriven.stateless.security; 2 | 3 | import org.springframework.security.core.AuthenticationException; 4 | import org.springframework.social.connect.*; 5 | import org.springframework.social.connect.mem.TemporaryConnectionRepository; 6 | 7 | import java.util.*; 8 | 9 | /** 10 | * A Simplified version of the JdbcUsersConnectionRepository that does not persist multiple connections to/from users. 11 | * This repository works with a one-to-one relation between between User and Connection 12 | * Note that it uses the JPA based UserService so no custom SQL is used 13 | */ 14 | public class SimpleUsersConnectionRepository implements UsersConnectionRepository { 15 | 16 | private SocialUserService userService; 17 | 18 | private ConnectionFactoryLocator connectionFactoryLocator; 19 | private ConnectionSignUp connectionSignUp; 20 | 21 | public SimpleUsersConnectionRepository(SocialUserService userService, ConnectionFactoryLocator connectionFactoryLocator) { 22 | this.userService = userService; 23 | this.connectionFactoryLocator = connectionFactoryLocator; 24 | } 25 | 26 | @Override 27 | public List findUserIdsWithConnection(Connection connection) { 28 | try { 29 | User user = userService.loadUserByConnectionKey(connection.getKey()); 30 | user.setAccessToken(connection.createData().getAccessToken()); 31 | userService.updateUserDetails(user); 32 | return Arrays.asList(user.getUserId()); 33 | } catch (AuthenticationException ae) { 34 | return Arrays.asList(connectionSignUp.execute(connection)); 35 | } 36 | } 37 | 38 | @Override 39 | public Set findUserIdsConnectedTo(String providerId, Set providerUserIds) { 40 | Set keys = new HashSet<>(); 41 | for (String userId: providerUserIds) { 42 | ConnectionKey ck = new ConnectionKey(providerId, userId); 43 | try { 44 | keys.add(userService.loadUserByConnectionKey(ck).getUserId()); 45 | } catch (AuthenticationException ae) { 46 | //ignore 47 | } 48 | } 49 | return keys; 50 | } 51 | 52 | @Override 53 | public ConnectionRepository createConnectionRepository(String userId) { 54 | final ConnectionRepository connectionRepository = new TemporaryConnectionRepository(connectionFactoryLocator); 55 | final User user = userService.loadUserByUserId(userId); 56 | final ConnectionData connectionData = new ConnectionData( 57 | user.getProviderId(), 58 | user.getProviderUserId(), 59 | null, null, null, 60 | user.getAccessToken(), 61 | null, null, null); 62 | 63 | final Connection connection = connectionFactoryLocator 64 | .getConnectionFactory(user.getProviderId()).createConnection(connectionData); 65 | connectionRepository.addConnection(connection); 66 | return connectionRepository; 67 | } 68 | 69 | public void setConnectionSignUp(ConnectionSignUp connectionSignUp) { 70 | this.connectionSignUp = connectionSignUp; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/jdriven/stateless/security/TokenHandler.java: -------------------------------------------------------------------------------- 1 | package com.jdriven.stateless.security; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.IOException; 5 | import java.security.InvalidKeyException; 6 | import java.security.NoSuchAlgorithmException; 7 | import java.util.Arrays; 8 | import java.util.Date; 9 | 10 | import javax.crypto.Mac; 11 | import javax.crypto.spec.SecretKeySpec; 12 | import javax.xml.bind.DatatypeConverter; 13 | 14 | import com.fasterxml.jackson.core.JsonProcessingException; 15 | import com.fasterxml.jackson.databind.ObjectMapper; 16 | 17 | public final class TokenHandler { 18 | 19 | private static final String HMAC_ALGO = "HmacSHA256"; 20 | private static final String SEPARATOR = "."; 21 | private static final String SEPARATOR_SPLITTER = "\\."; 22 | 23 | private final Mac hmac; 24 | 25 | public TokenHandler(byte[] secretKey) { 26 | try { 27 | hmac = Mac.getInstance(HMAC_ALGO); 28 | hmac.init(new SecretKeySpec(secretKey, HMAC_ALGO)); 29 | } catch (NoSuchAlgorithmException | InvalidKeyException e) { 30 | throw new IllegalStateException("failed to initialize HMAC: " + e.getMessage(), e); 31 | } 32 | } 33 | 34 | public User parseUserFromToken(String token) { 35 | final String[] parts = token.split(SEPARATOR_SPLITTER); 36 | if (parts.length == 2 && parts[0].length() > 0 && parts[1].length() > 0) { 37 | try { 38 | final byte[] userBytes = fromBase64(parts[0]); 39 | final byte[] hash = fromBase64(parts[1]); 40 | 41 | boolean validHash = Arrays.equals(createHmac(userBytes), hash); 42 | if (validHash) { 43 | final User user = fromJSON(userBytes); 44 | if (new Date().getTime() < user.getExpires()) { 45 | return user; 46 | } 47 | } 48 | } catch (IllegalArgumentException e) { 49 | //log tempering attempt here 50 | } 51 | } 52 | return null; 53 | } 54 | 55 | public String createTokenForUser(User user) { 56 | byte[] userBytes = toJSON(user); 57 | byte[] hash = createHmac(userBytes); 58 | final StringBuilder sb = new StringBuilder(170); 59 | sb.append(toBase64(userBytes)); 60 | sb.append(SEPARATOR); 61 | sb.append(toBase64(hash)); 62 | return sb.toString(); 63 | } 64 | 65 | private User fromJSON(final byte[] userBytes) { 66 | try { 67 | return new ObjectMapper().readValue(new ByteArrayInputStream(userBytes), User.class); 68 | } catch (IOException e) { 69 | throw new IllegalStateException(e); 70 | } 71 | } 72 | 73 | private byte[] toJSON(User user) { 74 | try { 75 | return new ObjectMapper().writeValueAsBytes(user); 76 | } catch (JsonProcessingException e) { 77 | throw new IllegalStateException(e); 78 | } 79 | } 80 | 81 | private String toBase64(byte[] content) { 82 | return DatatypeConverter.printBase64Binary(content).replace('+', '-').replace('/', '_').replaceAll("=", ""); 83 | } 84 | 85 | private byte[] fromBase64(String urlsafeBase64) { 86 | urlsafeBase64 = urlsafeBase64.replace('-', '+').replace('_', '/'); 87 | final int rest = urlsafeBase64.length() % 4; 88 | if (rest != 0) { 89 | urlsafeBase64 += rest == 3 ? "=" : "=="; 90 | } 91 | return DatatypeConverter.parseBase64Binary(urlsafeBase64); 92 | } 93 | 94 | // synchronized to guard internal hmac object 95 | private synchronized byte[] createHmac(byte[] content) { 96 | return hmac.doFinal(content); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 1.2.0.RELEASE 10 | 11 | 12 | 13 | stateless 14 | boot-stateless-social 15 | 0.0.1-SNAPSHOT 16 | 17 | war 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-autoconfigure 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-web 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-data-jpa 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-security 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-social-facebook 39 | 40 | 41 | org.springframework.social 42 | spring-social-security 43 | 44 | 45 | org.hibernate 46 | hibernate-validator 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-starter-test 52 | test 53 | 54 | 55 | 62 | 63 | com.h2database 64 | h2 65 | runtime 66 | 67 | 68 | 69 | org.springframework.boot 70 | spring-boot-starter-tomcat 71 | provided 72 | 73 | 74 | 75 | 76 | 77 | 78 | maven-compiler-plugin 79 | 80 | 1.7 81 | 1.7 82 | 83 | 84 | 85 | org.springframework.boot 86 | spring-boot-maven-plugin 87 | 88 | 89 | 90 | 91 | 92 | 93 | spring-releases 94 | http://repo.spring.io/release 95 | 96 | 97 | spring-milestones 98 | http://repo.spring.io/milestone 99 | 100 | 101 | spring-snapshots 102 | http://repo.spring.io/snapshot 103 | 104 | 105 | 106 | 107 | spring-releases 108 | http://repo.spring.io/release 109 | 110 | 111 | spring-milestones 112 | http://repo.spring.io/milestone 113 | 114 | 115 | spring-snapshots 116 | http://repo.spring.io/snapshot 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /src/main/java/com/jdriven/stateless/security/StatelessAuthenticationSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.jdriven.stateless.security; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.core.annotation.Order; 7 | import org.springframework.http.HttpMethod; 8 | import org.springframework.security.authentication.AuthenticationManager; 9 | import org.springframework.security.config.annotation.ObjectPostProcessor; 10 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 11 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 12 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 13 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 14 | import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; 15 | import org.springframework.social.UserIdSource; 16 | import org.springframework.social.security.SocialAuthenticationFilter; 17 | import org.springframework.social.security.SpringSocialConfigurer; 18 | 19 | @EnableWebSecurity 20 | @Configuration 21 | @Order(1) 22 | public class StatelessAuthenticationSecurityConfig extends WebSecurityConfigurerAdapter { 23 | 24 | @Autowired 25 | private SocialAuthenticationSuccessHandler socialAuthenticationSuccessHandler; 26 | 27 | @Autowired 28 | private StatelessAuthenticationFilter statelessAuthenticationFilter; 29 | 30 | @Autowired 31 | private UserIdSource userIdSource; 32 | 33 | @Autowired 34 | private SocialUserService userService; 35 | 36 | public StatelessAuthenticationSecurityConfig() { 37 | super(true); 38 | } 39 | 40 | @Override 41 | protected void configure(HttpSecurity http) throws Exception { 42 | // Set a custom successHandler on the SocialAuthenticationFilter 43 | final SpringSocialConfigurer socialConfigurer = new SpringSocialConfigurer(); 44 | socialConfigurer.addObjectPostProcessor(new ObjectPostProcessor() { 45 | @Override 46 | public O postProcess(O socialAuthenticationFilter) { 47 | socialAuthenticationFilter.setAuthenticationSuccessHandler(socialAuthenticationSuccessHandler); 48 | return socialAuthenticationFilter; 49 | } 50 | }); 51 | 52 | http.exceptionHandling().and().anonymous().and().servletApi().and().headers().cacheControl().and() 53 | .authorizeRequests() 54 | 55 | //allow anonymous font and template requests 56 | .antMatchers("/").permitAll() 57 | .antMatchers("/favicon.ico").permitAll() 58 | .antMatchers("/resources/**").permitAll() 59 | 60 | //allow anonymous calls to social login 61 | .antMatchers("/auth/**").permitAll() 62 | 63 | //allow anonymous GETs to API 64 | .antMatchers(HttpMethod.GET, "/api/**").permitAll() 65 | 66 | //defined Admin only API area 67 | .antMatchers("/admin/**").hasRole("ADMIN") 68 | 69 | //all other request need to be authenticated 70 | .antMatchers(HttpMethod.GET, "/api/users/current/details").hasRole("USER") 71 | .anyRequest().hasRole("USER").and() 72 | 73 | // add custom authentication filter for complete stateless JWT based authentication 74 | .addFilterBefore(statelessAuthenticationFilter, AbstractPreAuthenticatedProcessingFilter.class) 75 | 76 | // apply the configuration from the socialConfigurer (adds the SocialAuthenticationFilter) 77 | .apply(socialConfigurer.userIdSource(userIdSource)); 78 | } 79 | 80 | @Bean 81 | @Override 82 | public AuthenticationManager authenticationManagerBean() throws Exception { 83 | return super.authenticationManagerBean(); 84 | } 85 | 86 | @Override 87 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 88 | auth.userDetailsService(userService); 89 | } 90 | 91 | @Override 92 | protected SocialUserService userDetailsService() { 93 | return userService; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/jdriven/stateless/security/User.java: -------------------------------------------------------------------------------- 1 | package com.jdriven.stateless.security; 2 | 3 | import java.util.EnumSet; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | 7 | import javax.persistence.*; 8 | import javax.validation.constraints.NotNull; 9 | import javax.validation.constraints.Size; 10 | 11 | import org.springframework.social.security.SocialUserDetails; 12 | 13 | import com.fasterxml.jackson.annotation.JsonIgnore; 14 | 15 | @Entity 16 | @Table(name = "user_account", uniqueConstraints = { @UniqueConstraint(columnNames = { "username" }) }) 17 | public class User implements SocialUserDetails { 18 | 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.SEQUENCE) 21 | private Long id; 22 | 23 | @NotNull 24 | @JsonIgnore 25 | private String providerId; 26 | 27 | @NotNull 28 | @JsonIgnore 29 | private String providerUserId; 30 | 31 | @NotNull 32 | @JsonIgnore 33 | private String accessToken; 34 | 35 | @NotNull 36 | @Size(min = 4, max = 30) 37 | private String username; 38 | 39 | @Transient 40 | private long expires; 41 | 42 | @NotNull 43 | private boolean accountExpired; 44 | 45 | @NotNull 46 | private boolean accountLocked; 47 | 48 | @NotNull 49 | private boolean credentialsExpired; 50 | 51 | @NotNull 52 | private boolean accountEnabled; 53 | 54 | @OneToMany(cascade = CascadeType.ALL, mappedBy = "user", fetch = FetchType.EAGER, orphanRemoval = true) 55 | private Set authorities; 56 | 57 | public Long getId() { 58 | return id; 59 | } 60 | 61 | public void setId(Long id) { 62 | this.id = id; 63 | } 64 | 65 | @Override 66 | @JsonIgnore 67 | public String getUserId() { 68 | return id.toString(); 69 | } 70 | 71 | @Override 72 | public String getUsername() { 73 | return username; 74 | } 75 | 76 | public void setUsername(String username) { 77 | this.username = username; 78 | } 79 | 80 | @Override 81 | @JsonIgnore 82 | public Set getAuthorities() { 83 | return authorities; 84 | } 85 | 86 | // Use Roles as external API 87 | public Set getRoles() { 88 | Set roles = EnumSet.noneOf(UserRole.class); 89 | if (authorities != null) { 90 | for (UserAuthority authority : authorities) { 91 | roles.add(UserRole.valueOf(authority)); 92 | } 93 | } 94 | return roles; 95 | } 96 | 97 | public void setRoles(Set roles) { 98 | for (UserRole role : roles) { 99 | grantRole(role); 100 | } 101 | } 102 | 103 | public void grantRole(UserRole role) { 104 | if (authorities == null) { 105 | authorities = new HashSet(); 106 | } 107 | authorities.add(role.asAuthorityFor(this)); 108 | } 109 | 110 | public void revokeRole(UserRole role) { 111 | if (authorities != null) { 112 | authorities.remove(role.asAuthorityFor(this)); 113 | } 114 | } 115 | 116 | public boolean hasRole(UserRole role) { 117 | return authorities.contains(role.asAuthorityFor(this)); 118 | } 119 | 120 | @Override 121 | @JsonIgnore 122 | public boolean isAccountNonExpired() { 123 | return !accountExpired; 124 | } 125 | 126 | @Override 127 | @JsonIgnore 128 | public boolean isAccountNonLocked() { 129 | return !accountLocked; 130 | } 131 | 132 | @Override 133 | @JsonIgnore 134 | public boolean isCredentialsNonExpired() { 135 | return !credentialsExpired; 136 | } 137 | 138 | @Override 139 | @JsonIgnore 140 | public boolean isEnabled() { 141 | return !accountEnabled; 142 | } 143 | 144 | public long getExpires() { 145 | return expires; 146 | } 147 | 148 | public void setExpires(long expires) { 149 | this.expires = expires; 150 | } 151 | 152 | @Override 153 | public String toString() { 154 | return getClass().getSimpleName() + ": " + getUsername(); 155 | } 156 | 157 | @Override 158 | @JsonIgnore 159 | public String getPassword() { 160 | throw new IllegalStateException("password should never be used"); 161 | } 162 | 163 | public String getProviderId() { 164 | return providerId; 165 | } 166 | 167 | public void setProviderId(String providerId) { 168 | this.providerId = providerId; 169 | } 170 | 171 | public String getProviderUserId() { 172 | return providerUserId; 173 | } 174 | 175 | public void setProviderUserId(String providerUserId) { 176 | this.providerUserId = providerUserId; 177 | } 178 | 179 | public String getAccessToken() { 180 | return accessToken; 181 | } 182 | 183 | public void setAccessToken(String accessToken) { 184 | this.accessToken = accessToken; 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/main/webapp/resources/js/angular-cookies.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.3.8 3 | * (c) 2010-2014 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) {'use strict'; 7 | 8 | /** 9 | * @ngdoc module 10 | * @name ngCookies 11 | * @description 12 | * 13 | * # ngCookies 14 | * 15 | * The `ngCookies` module provides a convenient wrapper for reading and writing browser cookies. 16 | * 17 | * 18 | *
19 | * 20 | * See {@link ngCookies.$cookies `$cookies`} and 21 | * {@link ngCookies.$cookieStore `$cookieStore`} for usage. 22 | */ 23 | 24 | 25 | angular.module('ngCookies', ['ng']). 26 | /** 27 | * @ngdoc service 28 | * @name $cookies 29 | * 30 | * @description 31 | * Provides read/write access to browser's cookies. 32 | * 33 | * Only a simple Object is exposed and by adding or removing properties to/from this object, new 34 | * cookies are created/deleted at the end of current $eval. 35 | * The object's properties can only be strings. 36 | * 37 | * Requires the {@link ngCookies `ngCookies`} module to be installed. 38 | * 39 | * @example 40 | * 41 | * ```js 42 | * angular.module('cookiesExample', ['ngCookies']) 43 | * .controller('ExampleController', ['$cookies', function($cookies) { 44 | * // Retrieving a cookie 45 | * var favoriteCookie = $cookies.myFavorite; 46 | * // Setting a cookie 47 | * $cookies.myFavorite = 'oatmeal'; 48 | * }]); 49 | * ``` 50 | */ 51 | factory('$cookies', ['$rootScope', '$browser', function($rootScope, $browser) { 52 | var cookies = {}, 53 | lastCookies = {}, 54 | lastBrowserCookies, 55 | runEval = false, 56 | copy = angular.copy, 57 | isUndefined = angular.isUndefined; 58 | 59 | //creates a poller fn that copies all cookies from the $browser to service & inits the service 60 | $browser.addPollFn(function() { 61 | var currentCookies = $browser.cookies(); 62 | if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl 63 | lastBrowserCookies = currentCookies; 64 | copy(currentCookies, lastCookies); 65 | copy(currentCookies, cookies); 66 | if (runEval) $rootScope.$apply(); 67 | } 68 | })(); 69 | 70 | runEval = true; 71 | 72 | //at the end of each eval, push cookies 73 | //TODO: this should happen before the "delayed" watches fire, because if some cookies are not 74 | // strings or browser refuses to store some cookies, we update the model in the push fn. 75 | $rootScope.$watch(push); 76 | 77 | return cookies; 78 | 79 | 80 | /** 81 | * Pushes all the cookies from the service to the browser and verifies if all cookies were 82 | * stored. 83 | */ 84 | function push() { 85 | var name, 86 | value, 87 | browserCookies, 88 | updated; 89 | 90 | //delete any cookies deleted in $cookies 91 | for (name in lastCookies) { 92 | if (isUndefined(cookies[name])) { 93 | $browser.cookies(name, undefined); 94 | } 95 | } 96 | 97 | //update all cookies updated in $cookies 98 | for (name in cookies) { 99 | value = cookies[name]; 100 | if (!angular.isString(value)) { 101 | value = '' + value; 102 | cookies[name] = value; 103 | } 104 | if (value !== lastCookies[name]) { 105 | $browser.cookies(name, value); 106 | updated = true; 107 | } 108 | } 109 | 110 | //verify what was actually stored 111 | if (updated) { 112 | updated = false; 113 | browserCookies = $browser.cookies(); 114 | 115 | for (name in cookies) { 116 | if (cookies[name] !== browserCookies[name]) { 117 | //delete or reset all cookies that the browser dropped from $cookies 118 | if (isUndefined(browserCookies[name])) { 119 | delete cookies[name]; 120 | } else { 121 | cookies[name] = browserCookies[name]; 122 | } 123 | updated = true; 124 | } 125 | } 126 | } 127 | } 128 | }]). 129 | 130 | 131 | /** 132 | * @ngdoc service 133 | * @name $cookieStore 134 | * @requires $cookies 135 | * 136 | * @description 137 | * Provides a key-value (string-object) storage, that is backed by session cookies. 138 | * Objects put or retrieved from this storage are automatically serialized or 139 | * deserialized by angular's toJson/fromJson. 140 | * 141 | * Requires the {@link ngCookies `ngCookies`} module to be installed. 142 | * 143 | * @example 144 | * 145 | * ```js 146 | * angular.module('cookieStoreExample', ['ngCookies']) 147 | * .controller('ExampleController', ['$cookieStore', function($cookieStore) { 148 | * // Put cookie 149 | * $cookieStore.put('myFavorite','oatmeal'); 150 | * // Get cookie 151 | * var favoriteCookie = $cookieStore.get('myFavorite'); 152 | * // Removing a cookie 153 | * $cookieStore.remove('myFavorite'); 154 | * }]); 155 | * ``` 156 | */ 157 | factory('$cookieStore', ['$cookies', function($cookies) { 158 | 159 | return { 160 | /** 161 | * @ngdoc method 162 | * @name $cookieStore#get 163 | * 164 | * @description 165 | * Returns the value of given cookie key 166 | * 167 | * @param {string} key Id to use for lookup. 168 | * @returns {Object} Deserialized cookie value. 169 | */ 170 | get: function(key) { 171 | var value = $cookies[key]; 172 | return value ? angular.fromJson(value) : value; 173 | }, 174 | 175 | /** 176 | * @ngdoc method 177 | * @name $cookieStore#put 178 | * 179 | * @description 180 | * Sets a value for given cookie key 181 | * 182 | * @param {string} key Id for the `value`. 183 | * @param {Object} value Value to be stored. 184 | */ 185 | put: function(key, value) { 186 | $cookies[key] = angular.toJson(value); 187 | }, 188 | 189 | /** 190 | * @ngdoc method 191 | * @name $cookieStore#remove 192 | * 193 | * @description 194 | * Remove given cookie 195 | * 196 | * @param {string} key Id of the key-value pair to delete. 197 | */ 198 | remove: function(key) { 199 | delete $cookies[key]; 200 | } 201 | }; 202 | 203 | }]); 204 | 205 | 206 | })(window, window.angular); 207 | --------------------------------------------------------------------------------