I'm implementing a somewhat simple OAuth2 secured web application according to the guide provided at https://spring.io/guides/tutorials/spring-boot-oauth2/
I need to set a few arbitrary cookies after a successful login to simplify things in my frontend browser application.
Currently I have a working setup that authenticates a user with a Google account utilizing OAuth2.
I intended to use HttpSecurity oauth2Login().successHandler() in my WebSecurityConfigurerAdapter configure() function however I have no ClientRegistrationRepository provided and I don't seem to be able to autowire it.
I couldn't seem to find any standard approach documented anywhere on how to add additional login success logic to the implementation presented in that guide.
This is my main application class, OAuth2 client is configured in the application.yml file.
#SpringBootApplication
#EnableOAuth2Client
public class RestApplication extends WebSecurityConfigurerAdapter {
#Autowired
LogoutSuccessHandler logoutHandler;
#Autowired
OAuth2ClientContext oauth2ClientContext;
public static void main(String[] args) {
SpringApplication.run(RestApplication.class, args);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.antMatcher("/**").authorizeRequests()
.antMatchers("/", "/login**", "/error**", "/webapp/**").permitAll()
.anyRequest().authenticated()
.and().addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class)
.logout().logoutSuccessUrl("/").invalidateHttpSession(true).clearAuthentication(true).deleteCookies("JSESSIONID").logoutSuccessHandler(logoutHandler)
// #formatter:on
}
private Filter ssoFilter() {
OAuth2ClientAuthenticationProcessingFilter authFilter = new OAuth2ClientAuthenticationProcessingFilter(
"/login");
OAuth2RestTemplate oAuthTemplate = new OAuth2RestTemplate(oAuth2ResourceDetails(), oauth2ClientContext);
UserInfoTokenServices tokenServices = new UserInfoTokenServices(oAuth2Resource().getUserInfoUri(),
oAuth2ResourceDetails().getClientId());
authFilter.setRestTemplate(oAuthTemplate);
tokenServices.setRestTemplate(oAuthTemplate);
authFilter.setTokenServices(tokenServices);
return authFilter;
}
#Bean
#ConfigurationProperties("oauth.client")
public AuthorizationCodeResourceDetails oAuth2ResourceDetails() {
return new AuthorizationCodeResourceDetails();
}
#Bean
#ConfigurationProperties("oauth.resource")
public ResourceServerProperties oAuth2Resource() {
return new ResourceServerProperties();
}
#Bean
public FilterRegistrationBean<OAuth2ClientContextFilter> oauth2ClientFilterRegistration(
OAuth2ClientContextFilter filter) {
FilterRegistrationBean<OAuth2ClientContextFilter> registration = new FilterRegistrationBean<OAuth2ClientContextFilter>();
registration.setFilter(filter);
registration.setOrder(-100);
return registration;
}
}
What would be the correct way to add logic that would happen once during a successful authentication, specifically after I have access to the user Principal object.
I've done some further digging in the OAuth2ClientAuthenticationProcessingFilter implementation and found the following possible solution.
It's possible to plug in a custom SessionAuthenticationStrategy which by default is not implemented. The interface documentation states the following:
Allows pluggable support for HttpSession-related behaviour when an authentication occurs.
I've changed the ssoFilter() to the following:
private Filter ssoFilter() {
OAuth2ClientAuthenticationProcessingFilter authFilter = new OAuth2ClientAuthenticationProcessingFilter(
"/login");
authFilter.setSessionAuthenticationStrategy(new SessionAuthenticationStrategy() {
#Override
public void onAuthentication(Authentication authentication, HttpServletRequest request,
HttpServletResponse response) throws SessionAuthenticationException {
LinkedHashMap<String, Object> userDets = (LinkedHashMap<String, Object>) ((OAuth2Authentication) authentication)
.getUserAuthentication().getDetails();
response.addCookie(new Cookie("authenticated", userDets.get("email").toString()));
}
});
OAuth2RestTemplate oAuthTemplate = new OAuth2RestTemplate(oAuth2ResourceDetails(), oauth2ClientContext);
UserInfoTokenServices tokenServices = new UserInfoTokenServices(oAuth2Resource().getUserInfoUri(),
oAuth2ResourceDetails().getClientId());
authFilter.setRestTemplate(oAuthTemplate);
tokenServices.setRestTemplate(oAuthTemplate);
authFilter.setTokenServices(tokenServices);
return authFilter;
}
Related
I have Spring application with Angular frontend, I secured the two sides with Keycloak 11.0.2, how can I get the token information when a request is sent from a frontend, for example I want to access the authenticated user information and attributes in Spring side, because I the returned request depends on the users attributes.
Below is my configuration in spring :
keycloak.auth-server-url=http://localhost:8080/auth
keycloak.realm=myapp
keycloak.resource=myapp-api
keycloak.ssl-required=external
keycloak.bearer-only=true
keycloak.principal-attribute=preferred_username
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(jsr250Enabled = true)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests().anyRequest().permitAll();
http.csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
#Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
}
Based upon your question, I think you are referring to the ID token, which you can get (for instance) from the request. Check the following example:
#GetMapping(path = "/student")
public String teen(HttpServletRequest request) throws ServletException {
KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) request.getUserPrincipal();
System.out.println("---- ROLES ----");
token.getAccount().getRoles().forEach(System.out::println);
Map<String, Object> otherClaims = token.getAccount().getKeycloakSecurityContext().getIdToken().getOtherClaims();
Enumeration<String> attributeNames = request.getAttributeNames();
while (attributeNames.hasMoreElements())
System.out.println(attributeNames.nextElement());
for(String s : otherClaims.keySet()){
System.out.println(s);
System.out.println(otherClaims.get(s).toString());
}
System.out.println("------------");
return "student";
}
The code above is just an example, but is showcases some on the API functionality.
I configured resource and authorization servers in one application. I use spring-security oauth2, with Resource Owner Password Credentials. Can I set up basic auth on the server side? I don't want to do it on the front-end.
I don't know what a part of the code I need to show...
When I want to receive a token I need to enter this in postman:
Can I configure it on the server side?
Authorization Server:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
private TokenStore tokenStore;
#Autowired
private JwtAccessTokenConverter jwtTokenEnhancer;
#Autowired
private UserApprovalHandler userApprovalHandler;
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore).tokenEnhancer(jwtTokenEnhancer).userApprovalHandler(userApprovalHandler)
.authenticationManager(authenticationManager)
.pathMapping("/oauth/token", "/login");
}
}
Resource Server:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "resource_id";
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID).stateless(false);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.cors().and().csrf().disable()
.authorizeRequests()
.antMatchers("/swagger-ui.html#").permitAll()
.antMatchers("/").authenticated()
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
}
Security config:
#Configuration
#EnableWebSecurity(debug = true)
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private ClientDetailsService clientDetailsService;
#Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(10);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtTokenEnhancer());
}
#Bean
protected JwtAccessTokenConverter jwtTokenEnhancer() {
converter.setSigningKey("Demo-Key-1");
return converter;
}
#Bean
#Autowired
public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore) {
TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
handler.setTokenStore(tokenStore);
handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
handler.setClientDetailsService(clientDetailsService);
return handler;
}
#Bean
#Autowired
public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception {
TokenApprovalStore store = new TokenApprovalStore();
store.setTokenStore(tokenStore);
return store;
}
#Override
#Order(Ordered.HIGHEST_PRECEDENCE)
protected void configure(final HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll()
.and().cors().and().csrf().disable();
}
}
This answer is accompanied by a complete and working sample.
Maybe you are biting off more than you can chew here?
For example:
.antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll()
The /oauth/token endpoint must remain protected. This is the endpoint on the authorization server that issues tokens to authenticated clients. The system will probably fail with NullpointerException or other exceptions if you open it, however, the above configuration option indicate that maybe you're a bit confused about how OAuth2 works.
What I would recommend is to first fully understand authorization server versus resource server. You can definitely combine the two, but they would have very different endpoints.
Authorization Server - typical endpoints
/oauth/token - issues tokens
/oauth/authorize - issues authorization codes
/introspect - validates a token and returns token claims in a known format
Resource Server - These would be your application endpoints, requiring Bearer tokens, for example
/account/123/debit
and these endpoints expect a stateless request that has an authorization header
Authorization: Bearer <token value here>
A controller for a resource server would look like this:
#PreAuthorize("hasRole('your-scope-role')")
#RequestMapping(value = "/hello")
#ResponseBody
public String hello(Principal principal) {
return "Hello to " + principal.getName();
}
Feel free to review the simple project that I have created for you.
In addition to that, I also recommend this video on OAuth2 and OpenID Connect
In my sample, I have configured the clients like this:
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
InMemoryClientDetailsService clientDetails = new InMemoryClientDetailsService();
BaseClientDetails client = new BaseClientDetails(
"testclient",
null,
"testscope,USER,ADMIN",
"password",
null
);
client.setClientSecret(passwordEncoder.encode("secret"));
clientDetails.setClientDetailsStore(
Collections.singletonMap(
client.getClientId(),
client
)
);
clients.withClientDetails(clientDetails);
}
Take a look at this simple test case, the client is using http-basic authentication:
mvc.perform(
post("/oauth/token")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.param("username", "admin")
.param("password", "password")
.param("grant_type", "password")
.param("response_type", "token")
.param("client_id", "testclient")
.header("Authorization", "Basic " + Base64.encodeBase64String("testclient:secret".getBytes()))
This is client authentication, using the http-basic method:
.header("Authorization", "Basic " + Base64.encodeBase64String("testclient:secret".getBytes()))
I am trying to front or "wrap" a RESTful API service application in front of a backend HTTP service. The backend service maintains authentication state via proprietary sessions. Authentication with the backend server is achieved through a convoluted conversation between the authenticating client and the server. In general, interaction with this server is non-standard and not good enough to expose to our customers as an API. However there is too much legacy to overhaul and re-engineed the entire HTTP interface.
My objective is to abstract the interface to this backend server by fronting this server with a RESTful server that implements OAuth2 authentication via password grant so that 3rd party APIs can interface with the backend server in a standard and well understood way.
I have built a simple REST SpringBoot application that can authenticate with the backend. The part I am battling with is figuring out how best to map authenticated tokens/requests to the backend's session IDs.
In essence I am trying to associate a bit of state on each authenticated authorization.
As a solution I've thought about implementing my own TokenStore which will maintain the mapping. Alternatively I have thought about implementing a "Custom Token Granter". However I have a strong intuition that there is a better and easier way to achieve my objective.
Additional information:
#EnableAuthorizationServer and #EnableResourceServer are in the same application.
Version information:
org.springframework.security.oauth:spring-security-oauth2 2.0.13.RELEASE
org.springframework.security:spring-security-config 4.2.2.RELEASE
org.springframework.boot:spring-boot-starter-security 1.5.3.RELEASE
This is my Resource Server Config:
#Configuration
#Profile(Common.CONST_EXTAPI_PROFILE)
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED).and()
.headers().frameOptions().disable().and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/", "/home","/register","/login", "/auth").permitAll()
.antMatchers(HttpMethod.OPTIONS,"/oauth/token").permitAll()
.antMatchers("/extapi/agent", "/extapi/agent/**", "/extapi/account/**", "/extapi/sale/**").authenticated();
}
}
This is my Authentication Server Config:
#Configuration
#Profile(Common.CONST_EXTAPI_PROFILE)
#EnableAuthorizationServer
public class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Order(-1)
public class MyWebSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll();
}
}
#Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyPair keyPair = new KeyStoreKeyFactory(new ClassPathResource("keystore.jks"), "foobar".toCharArray())
.getKeyPair("test");
converter.setKeyPair(keyPair);
return converter;
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception
{
clients.inMemory()
.withClient("acme")
.autoApprove(true)
.secret("acmesecret")
.authorizedGrantTypes("password", "authorization_code", "refresh_token").scopes("openid");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.accessTokenConverter(jwtAccessTokenConverter());
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess(
"isAuthenticated()");
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
}
And the Custom Authentication Provider
#Component
#Profile(Common.CONST_EXTAPI_PROFILE)
public class CustomAuthenticationProvider implements AuthenticationProvider
{
#Autowired
private LoginSessionData sessionData;
#Autowired
private GuiAuthenticationService authService;
#Autowired
private ContextService contextService;
#Autowired
private RSAEncryptionService rsaService;
#Autowired
private ObjectMapper mapper;
#Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
final String username = auth.getName();
final String password = auth.getCredentials().toString();
Authentication result = null;
DataWrapper oauthResponse = new DataWrapper();
try
{
BaseResponse resp = authService.authenticate(contextService.getCompanyID(), false, null);
oauthResponse.setAuth(rsaService.getPublicKey());
oauthResponse.setData(mapper.writeValueAsString(resp));
if(resp.getState().equals("REQUIRE_UTF8_USERNAME")){
BaseRequest request = new BaseRequest();
request.setData(username);
request.setCid(Integer.toString(contextService.getCompanyID()));
sessionData.captureData(request);
resp = authService.process(request);
}
if(resp.getState().equals("REQUIRE_RSA_PASSWORD"))
{
BaseRequest request = new BaseRequest();
request.setData(password);
request.setCid(Integer.toString(contextService.getCompanyID()));
resp = authService.process(request);
}
if(resp.getState().equals("AUTHENTICATED"))
{
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(username, password, Collections.emptyList());
result = token;
}
}
catch (Exception e)
{
sessionData.setCurrentState(AuthenticationState.INVALID);
oauthResponse.setError(e.getLocalizedMessage());
oauthResponse.setData(Common.CONST_FATAL_ERROR);
e.printStackTrace();
}
if(result == null)
throw new BadCredentialsException("External system authentication failed");
return result;
}
#Override
public boolean supports(Class<?> auth) {
return auth.equals(UsernamePasswordAuthenticationToken.class);
}
}
I am trying to protect my microservices on Spring Boot using Oath2 with Client Credentials flow.
By the way, those microservices will only talk each other over the middleware layer, I mean no user credentials are needed to allow the authorization (user login process as Facebook).
I have looked for samples on the Internet showing how to create an authorization and resource server to manage this communication. However I just found examples explaining how to do it using user credentials (three legs).
Does anyone have any sample how to do it in Spring Boot and Oauth2? If it is possible give further details about the scopes used, token exchanging would be grateful.
We have REST services protected with Oauth2 Client credentials scheme. The Resource and authorization service are running in the same app, but can be split into different apps.
#Configuration
public class SecurityConfig {
#Configuration
#EnableResourceServer
protected static class ResourceServer extends ResourceServerConfigurerAdapter {
// Identifies this resource server. Usefull if the AuthorisationServer authorises multiple Resource servers
private static final String RESOURCE_ID = "*****";
#Resource(name = "OAuth")
#Autowired
DataSource dataSource;
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.authorizeRequests().anyRequest().authenticated();
// #formatter:on
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID);
resources.tokenStore(tokenStore());
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
}
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Resource(name = "OAuth")
#Autowired
DataSource dataSource;
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore());
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
}
}
Datasource config for the Oauth2 tables:
#Bean(name = "OAuth")
#ConfigurationProperties(prefix="datasource.oauth")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
Communicating with authentication & resource server goes as followed
curl -H "Accept: application/json" user:password#localhost:8080/oauth/token -d grant_type=client_credentials
curl -H "Authorization: Bearer token" localhost:8080/...
The following record is present in the Oauth2 Database:
client_id resource_ids client_secret scope authorized_grant_types web_server_redirect_uri authorities access_token_validity refresh_token_validity additional_information autoapprove
user **** password NULL client_credentials NULL X NULL NULL NULL NULL
Resttemplate configuration in client application
#Configuration
#EnableOAuth2Client
public class OAuthConfig {
#Value("${OAuth2ClientId}")
private String oAuth2ClientId;
#Value("${OAuth2ClientSecret}")
private String oAuth2ClientSecret;
#Value("${Oauth2AccesTokenUri}")
private String accessTokenUri;
#Bean
public RestTemplate oAuthRestTemplate() {
ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails();
resourceDetails.setId("1");
resourceDetails.setClientId(oAuth2ClientId);
resourceDetails.setClientSecret(oAuth2ClientSecret);
resourceDetails.setAccessTokenUri(accessTokenUri);
/*
When using #EnableOAuth2Client spring creates a OAuth2ClientContext for us:
"The OAuth2ClientContext is placed (for you) in session scope to keep the state for different users separate.
Without that you would have to manage the equivalent data structure yourself on the server,
mapping incoming requests to users, and associating each user with a separate instance of the OAuth2ClientContext."
(http://projects.spring.io/spring-security-oauth/docs/oauth2.html#client-configuration)
Internally the SessionScope works with a threadlocal to store variables, hence a new thread cannot access those.
Therefore we can not use #Async
Solution: create a new OAuth2ClientContext that has no scope.
*Note: this is only safe when using client_credentials as OAuth grant type!
*/
// OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails, oauth2ClientContext);
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails, new DefaultOAuth2ClientContext());
return restTemplate;
}
}
You can inject the restTemplate to talk (Asynchronously) to the Oauth2 secured service.
We do not use scope at the moment.
Update with Spring-boot-2.7 and Java 17.
https://chuangtc.com/Java/spring-boot-27-security-social-login.php
public class SecurityConfig {
#Value("${spring.social.facebook.appSecret}")
String appSecret;
#Value("${spring.social.facebook.appId}")
String appId;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private FacebookConnectionSignup facebookConnectionSignup;
#Bean
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(userDetailsService)
.and()
.build();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeRequests()
.antMatchers("/login*", "/signin/**", "/signup/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout();
return http.build();
}
#Bean
// #Primary
public ProviderSignInController providerSignInController() {
ConnectionFactoryLocator connectionFactoryLocator = connectionFactoryLocator();
UsersConnectionRepository usersConnectionRepository = getUsersConnectionRepository(connectionFactoryLocator);
((InMemoryUsersConnectionRepository) usersConnectionRepository).setConnectionSignUp(facebookConnectionSignup);
return new ProviderSignInController(connectionFactoryLocator, usersConnectionRepository, new FacebookSignInAdapter());
}
private ConnectionFactoryLocator connectionFactoryLocator() {
ConnectionFactoryRegistry registry = new ConnectionFactoryRegistry();
registry.addConnectionFactory(new FacebookConnectionFactory(appId, appSecret));
return registry;
}
private UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
return new InMemoryUsersConnectionRepository(connectionFactoryLocator);
}
}
FacebookSignInAdapter
#Service
public class FacebookSignInAdapter implements SignInAdapter {
#Override
public String signIn(String localUserId, Connection<?> connection, NativeWebRequest request) {
System.out.println(" ====== Sign In adapter");
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(connection.getDisplayName(), null, Arrays.asList(new SimpleGrantedAuthority("FACEBOOK_USER"))));
return null;
}
}
at the moment I am trying to integrate Spring-Security-Oauth2, Zuul, OpenAM as OAuth2 authorization Server and a WCF REST API as resource Server. The final Setup should look something like the following:
I read the tutorial, which explains how to setup a SSO Environment with spring and AngularJS (sso with spring and angularJS), however in my case I would like to use OpenAM and password grant flow to authenticate useres.
So in the Spring Boot application my current config Looks as follows:
#SpringBootApplication
#EnableZuulProxy
#EnableOAuth2Client
public class ApolloUIProxyApplication {
public static void main(String[] args) {
SpringApplication.run(ApolloUIProxyApplication.class, args);
}
#Configuration
protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.logout().and().antMatcher("/**").authorizeRequests()
.antMatchers("/index.html", "/home.html", "/", "/login").permitAll()
.anyRequest().authenticated().and().csrf()
.csrfTokenRepository(csrfTokenRepository()).and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class)
.addFilterAfter(authenticationProcessingFilter(), CsrfFilter.class);
}
#Bean
public ZuulFilter tokenRelayFilter(){
JwtTokenRelayFilter filter = new JwtTokenRelayFilter();
filter.setRestTemplate(restTemplate());
return new JwtTokenRelayFilter();
}
#Bean
public ZuulFilter customTokenFilter(){
return new CustomZuulFilter();
}
#Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
return new JwtAccessTokenConverter();
}
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class
.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null
&& !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
private OAuth2ClientAuthenticationProcessingFilter authenticationProcessingFilter(){
OAuth2ClientAuthenticationProcessingFilter processingFilter = new OAuth2ClientAuthenticationProcessingFilter("/login");
processingFilter.setRestTemplate(restTemplate());
processingFilter.setTokenServices(resourceServerTokenServices());
return processingFilter;
}
#Bean
public ResourceServerTokenServices resourceServerTokenServices(){
OpenAMRemoteTokenService remoteTokenServices = new OpenAMRemoteTokenService();
remoteTokenServices.setRestTemplate(restTemplate());
remoteTokenServices.setAccessTokenConverter(accessTokenConverter());
remoteTokenServices.setClientId("...");
remoteTokenServices.setClientSecret("...");
remoteTokenServices.setCheckTokenEndpointUrl("http://...");
return remoteTokenServices;
}
#Bean
public OAuth2RestTemplate restTemplate(){
OAuth2RestTemplate template = new OAuth2RestTemplate(resourceDetails(), clientContext());
return template;
}
#Bean
public AccessTokenProvider accessTokenProvider(){
ResourceOwnerPasswordAccessTokenProvider provider = new ResourceOwnerPasswordAccessTokenProvider();
return provider;
}
#Bean
public OAuth2ClientContext clientContext(){
return new OpenAMClientContext();
}
#Bean
public OAuth2ProtectedResourceDetails resourceDetails(){
ResourceOwnerPasswordResourceDetails details = new ResourceOwnerPasswordResourceDetails();
details.setGrantType("password");
details.setAccessTokenUri("http://...");
details.setScope(Arrays.asList("openid");
details.setClientId("...");
details.setClientSecret("...");
return details;
}
#Bean
public AccessTokenConverter accessTokenConverter(){
DefaultAccessTokenConverter tokenConverter = new DefaultAccessTokenConverter();
tokenConverter.setUserTokenConverter(userAuthenticationConverter());
return tokenConverter;
}
#Bean
public UserAuthenticationConverter userAuthenticationConverter(){
return new OpenAMUserAuthenticationConverter();
}
}
}
I wrote a custom RemoteTokenService, because otherwise Spring could not access the tokeninfo endpoint of OpenAM, which requires a GET-request and not a Post. This Connection works fine now, so that i get a valid access-token from OpenAM and can also query the tokeninfo.endpoint for token/user-Infos. The Authentication object gets created and is stored in the security-context of Spring. I can also access the Authentication Object in the ZuulFilter.
My Problem now is, that i had to tweek the "OAuth2ClientContext" to grab the users credentials from the servletRequest and put it on the "AccessTokenRequest". Otherwise I would have to hard-code them in the ResourceDetails, which is not appropriate in my case.
The result is, that the ClientContext (and also AccessTokenRequest I guess) is shared between all users of the System. What i want is a session scoped Client Context, so that I can have multiple useres logged in and can access the right SecurityContext for each user on every request.
So my question are,
1) how can I make the ClientContext and AccessTokenRequest session scoped?
2) Do I need to use Spring Session module?
3) Do I need to set the sessionStrategy
Thank you!