Using tls with jwt - java

I want to use tls with my sharing of token and login process. I implemented it and it works fine. I create a new port for tls that is https://localhost:8443 but there is a security leak in my app. When i post https://localhost:8443/login and take the jwt and use it with GET https://localhost:8443/welcome everything great if i use http instead of https bad request is returned. But if i change my url with http://localhost:8080/welcome and gives the token that is taken before the app must return bad request but it returns succesful open. My server and security config is below. What am i doing wrong?
Server Config:
#Configuration
public class ServerConfig {
#Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
#Override
protected void postProcessContext(Context context) {
SecurityConstraint securityConstraint = new SecurityConstraint();
securityConstraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/*");
securityConstraint.addCollection(collection);
context.addConstraint(securityConstraint);
}
};
tomcat.addAdditionalTomcatConnectors(getHttpConnector());
return tomcat;
}
private Connector getHttpConnector() {
Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
connector.setScheme("http");
connector.setPort(8080);
connector.setSecure(false);
connector.setRedirectPort(8443);
return connector;
}
}
Security Config:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private JwtTokenFilter jwtTokenFilter;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
public void configurePasswordEncoder(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService).passwordEncoder(getBCryptPasswordEncoder());
}
#Bean
public BCryptPasswordEncoder getBCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public AuthenticationManager getAuthenticationManager() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
}
Auth Controller:
#RestController
#RequestMapping("/login")
public class AuthConroller {
#Autowired
private TokenManager tokenManager;
#Autowired
private AuthenticationManager authenticationManager;
#PostMapping
public ResponseEntity<String> login(#RequestBody LoginRequest loginRequest) {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
return ResponseEntity.ok(tokenManager.generateToken(loginRequest.getUsername()));
} catch (Exception e) {
throw e;
}
}
}
Message Controller:
#RestController
#RequestMapping("/message")
public class MessageController {
#GetMapping
public ResponseEntity<String> getMessage() {
return ResponseEntity.ok("JWT demo");
}
}

Related

How to extract claims from token in Resource Server, in Spring Boot

My authentication server is configured to retrieve check credentials against a table on my database, with a token enhancer which I use to pass additional claims - access control related stuff.
As such, I've written it like this:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Value("${security.signing-key}")
private String signingKey;
private #Autowired TokenStore tokenStore;
private #Autowired AuthenticationManager authenticationManager;
private #Autowired CustomUserDetailsService userDetailsService;
private #Autowired JwtAccessTokenConverter accessTokenConverter;
private static final Logger LOGGER = LogManager.getLogger(AuthorizationServerConfig.class);
#Bean
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource oauthDataSource() {
DataSource ds = null;
try {
Context initialContex = new InitialContext();
ds = (DataSource) (initialContex.lookup("java:/jdbc/oauthdatasource"));
if (ds != null) {
ds.getConnection();
}
} catch (NamingException ex) {
LOGGER.error("Naming exception thrown: ", ex);
} catch (SQLException ex) {
LOGGER.info("SQL exception thrown: ", ex);
}
return ds;
}
#Bean
public JdbcClientDetailsService clientDetailsServices() {
return new JdbcClientDetailsService(oauthDataSource());
}
#Bean
public TokenStore tokenStore() {
return new CustomJdbcTokenStore(oauthDataSource());
}
#Bean
public ApprovalStore approvalStore() {
return new JdbcApprovalStore(oauthDataSource());
}
#Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(oauthDataSource());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
CustomTokenEnhancer converter = new CustomTokenEnhancer();
converter.setSigningKey(signingKey);
return converter;
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsServices());
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.accessTokenConverter(accessTokenConverter)
.userDetailsService(userDetailsService)
.reuseRefreshTokens(false);
}
}
This works very fine. When I make a call via POSTMAN, I get something like this:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic2hhcmVwb3J0YWwiXSwiaW5mb19maXJzdCI6IlRoaXMgaXMgdGhlIGZpcnN0IEluZm8iLCJ1c2VyX25hbWUiOiJBdXRoZW50aWNhdGlvbiIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSIsInRydXN0Il0sImluZm9fc2Vjb25kIjoiVGhpcyBpcyB0aGUgc2Vjb25kIGluZm8iLCJleHAiOjE1ODA3MTMyOTQsImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiJdLCJqdGkiOiI1MTg4MGJhZC00MGJiLTQ3ZTItODRjZS1lNDUyNGY1Y2Y3MzciLCJjbGllbnRfaWQiOiJzaGFyZXBvcnRhbC1jbGllbnQifQ.ABmBjwmVDb2acZtGSQrjKcCwfZwhw4R_rpW4y5JA1jY",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic2hhcmVwb3J0YWwiXSwiaW5mb19maXJzdCI6IlRoaXMgaXMgdGhlIGZpcnN0IEluZm8iLCJ1c2VyX25hbWUiOiJBdXRoZW50aWNhdGlvbiIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSIsInRydXN0Il0sImF0aSI6IjUxODgwYmFkLTQwYmItNDdlMi04NGNlLWU0NTI0ZjVjZjczNyIsImluZm9fc2Vjb25kIjoiVGhpcyBpcyB0aGUgc2Vjb25kIGluZm8iLCJleHAiOjE1ODA3MTM0MzQsImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiJdLCJqdGkiOiIyZDYxMDU2ZC01ZDMwLTRhZTQtOWMxZC0zZjliYjRiOWYxOGIiLCJjbGllbnRfaWQiOiJzaGFyZXBvcnRhbC1jbGllbnQifQ.qSLpJm4QxZTIVn1WYWH7EFBS8ryjF1hsD6RSRrEBZd0",
"expires_in": 359,
"scope": "read write trust"
}
The problem now is my resource server. This is how it used to be before I added a token enhancer to my authentication server:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private #Autowired CustomAuthenticationEntryPoint entryPoint;
private #Autowired TokenStore tokenStore;
private static final String RESOURCE_ID = "resourceid";
private static final Logger LOGGER = LogManager.getLogger(ResourceServerConfig.class);
#Bean
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource oauthDataSource() {
DataSource ds = null;
try {
Context initialContex = new InitialContext();
ds = (DataSource) (initialContex.lookup("java:/jdbc/oauthdatasource"));
if (ds != null) {
ds.getConnection();
}
} catch (NamingException ex) {
LOGGER.error("Naming exception thrown: ", ex);
} catch (SQLException ex) {
LOGGER.info("SQL exception thrown: ", ex);
}
return ds;
}
#Bean
public TokenStore getTokenStore() {
return new JdbcTokenStore(oauthDataSource());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/**").access("#oauth2.hasScope('read')")
.antMatchers(HttpMethod.POST, "/**").access("#oauth2.hasScope('write')")
.antMatchers(HttpMethod.PATCH, "/**").access("#oauth2.hasScope('write')")
.antMatchers(HttpMethod.PUT, "/**").access("#oauth2.hasScope('write')")
.antMatchers(HttpMethod.DELETE, "/**").access("#oauth2.hasScope('write')")
.and()
.headers().addHeaderWriter((request, response) -> {
response.addHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization");
if (request.getMethod().equals("OPTIONS")) {
response.setStatus(HttpServletResponse.SC_OK);
}
})
.and().exceptionHandling().authenticationEntryPoint(entryPoint);
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID).tokenStore(tokenStore).authenticationEntryPoint(entryPoint);
}
}
I wish to retrieve the access control information I've placed as additional claims via the authentication server, but I don't know how to go about it.
I saw a couple of examples on the internet, including this: How to extract claims from Spring Security OAuht2 Boot in the Resource Server?, but none of them are working for me. Or maybe I'm missing something.
Please, what do I have to add to make this possible?
I had to use a third-party library to achieve this.
This is the link to the library: https://github.com/auth0/java-jwt
It works really well.
In my resource server, I can get my token value, and then using the java-jwt library, I can extract any claims I've set in my authorization server:
public Map<String, Claim> getClaims() {
Map<String, Claim> claims = new HashMap<>();
String tokenValue = ((OAuth2AuthenticationDetails)((OAuth2Authentication) authenticationFacade.getAuthentication()).getDetails()).getTokenValue();
try {
DecodedJWT jwt = JWT.decode(tokenValue);
claims = jwt.getClaims();
} catch (JWTDecodeException ex) {
LOGGER.info("Error decoding token value");
LOGGER.error("Error decoding token value", ex);
}
return claims;
}
You should look at the documentation for java-jwt to learn more.

Set basic auth on server side

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()))

Spring JWT signature Validation fails

I am building microservice applications with spring cloud, oauth and JWT. My Oauth2 server generates JWT token but when I am trying to validate the token in gateway (implemented using ZUUL) I am getting below Error
Could you please let me know what is wrong and what could be the solution.
I am using Spring 4.3, Spring boot 1.5.8, Spring cloud Dalston.SR4
org.springframework.security.jwt.crypto.sign.InvalidSignatureException: Calculated signature did not match actual value
at org.springframework.security.jwt.crypto.sign.MacSigner.verify(MacSigner.java:62) ~[spring-security-jwt-1.0.8.RELEASE.jar:na]
at org.springframework.security.jwt.JwtImpl.verifySignature(JwtHelper.java:287) ~[spring-security-jwt-1.0.8.RELEASE.jar:na]
at org.springframework.security.jwt.JwtHelper.decodeAndVerify(JwtHelper.java:77) ~[spring-security-jwt-1.0.8.RELEASE.jar:na]
at com.debopam.gateway.filter.CustomPostZuulFilter.run(CustomPostZuulFilter.java:57) ~[classes/:na]
at com.netflix.zuul.ZuulFilter.runFilter(ZuulFilter.java:112) [zuul-core-1.3.0.jar:1.3.0]
at com.netflix.zuul.FilterProcessor.processZuulFilter(FilterProcessor.java:193) [zuul-core-1.3.0.jar:1.3.0]
at com.netflix.zuul.FilterProcessor.runFilters(FilterProcessor.java:157) [zuul-core-1.3.0.jar:1.3.0]
at com.netflix.zuul.FilterProcessor.postRoute(FilterProcessor.java:92) [zuul-core-1.3.0.jar:1.3.0]
at com.netflix.zuul.ZuulRunner.postRoute(ZuulRunner.java:87) [zuul-core-1.3.0.jar:1.3.0]
I have used signing key 12345AsDfG in both Auth server and gateway server.
Below Are the code snippet
Auth Server
#Configuration
public class JWTTokenStoreConfig {
#Autowired
private ServiceConfig serviceConfig;
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setAccessTokenValiditySeconds(60*30);
return defaultTokenServices;
}
#Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(serviceConfig.getJwtSigningKey());
return converter;
}
#Bean
public TokenEnhancer jwtTokenEnhancer() {
return new JWTTokenEnhancer();
}
}
#Configuration
public class JWTOAuth2Config extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private TokenStore tokenStore;
#Autowired
private DefaultTokenServices tokenServices;
#Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
#Autowired
private TokenEnhancer jwtTokenEnhancer;
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception
{
//oauthServer.checkTokenAccess("permitAll()");
oauthServer
.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')")
.checkTokenAccess("permitAll()");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer, jwtAccessTokenConverter));
endpoints.tokenStore(tokenStore) //JWT
.accessTokenConverter(jwtAccessTokenConverter) //JWT
.tokenEnhancer(tokenEnhancerChain) //JWT
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("uiapp")
.secret("secret")
.authorizedGrantTypes("refresh_token", "password", "client_credentials")
.scopes("webclient", "mobileclient");
}
}
In the Gateway application, I am using below code to verify the Token
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
try {
InputStream is = ctx.getResponseDataStream();
String responseBody = IOUtils.toString(is);
if (StringUtils.hasText(responseBody)
&& responseBody.contains("access_token")) {
Map<String, Object> responseMap = objectMapper.readValue(
responseBody, new TypeReference<Map<String, Object>>() {});
String accesToken = responseMap.get("access_token").toString();
Jwt jwt = JwtHelper.decodeAndVerify(accesToken, new MacSigner(serviceConfig.getJwtSigningKey()));
System.out.println(jwt.getClaims());
//System.out.println(jwt.getBody());
}
ctx.setResponseBody(responseBody);
} catch (Exception e) {
logger.error("Error occured in zuul post filter", e);
}
return null;
}
There was a signing key mismatch between the services.

Interceptor for Social Configurer (SocialAuthenticationFilter) : Spring Social

Functionality is working fine, but I am not able to add an interceptor while using SocialAuthenticationFilter(similar to the interceptor we can add for ConnectController).
Especially I want to add display:popup in the facebook oauth reuqest.
Please let me know if you have any solution.
Thanks
Security config:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().loginPage("/login").failureUrl("/login/error")
.loginProcessingUrl("/login/authenticate").defaultSuccessUrl("/login/success")
.and().logout().logoutUrl("/logout").logoutSuccessUrl("/login?logout").deleteCookies("JSESSIONID")
.and().authorizeRequests().antMatchers("/", "/register/**", "/login/**", "/faq").permitAll()
.antMatchers("/favicon.ico", "/resources/**").permitAll()
.antMatchers("/fonts/**", "/img/**", "/css/**", "/js/**", "/templates/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/**").authenticated()
.and().exceptionHandling().accessDeniedPage("/error/403")
.and().csrf().disable().headers()
.addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN))
.and().rememberMe()
.and().apply(getSpringSocialConfigurer());
}
private SpringSocialConfigurer getSpringSocialConfigurer() {
SpringSocialConfigurer config = new SpringSocialConfigurer();
config.postLoginUrl("/dashboard");
config.defaultFailureUrl("/error/403");
config.signupUrl("/register/social");
return config;
}
social config class:
#Configuration
#EnableSocial
public class SocialConfig extends SocialConfigurerAdapter {
#Inject
private DataSource dataSource;
#Override
public void addConnectionFactories(ConnectionFactoryConfigurer cfConfig, Environment env) {
//cfConfig.addConnectionFactory(new TwitterConnectionFactory(env.getProperty("twitter.appKey"), env.getProperty("twitter.appSecret")));
cfConfig.addConnectionFactory(new FacebookConnectionFactory(env.getProperty("facebook.appKey"), env.getProperty("facebook.appSecret")));
}
#Override
public UserIdSource getUserIdSource() {
return new AuthenticationNameUserIdSource();
}
#Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
return new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
}
#Bean
#Scope(value="request", proxyMode= ScopedProxyMode.INTERFACES)
public Facebook facebook(ConnectionRepository repository) {
Connection<Facebook> connection = repository.findPrimaryConnection(Facebook.class);
return connection != null ? connection.getApi() : null;
}
}

Change password grant to implicit grant with Spring Security

I have a working Spring Boot 1.3.1 application with an AngularJS client using the "password" grant type with OAuth2. I know this is not very good as the client_id and client_secret are visible in the AngularJS code by everybody.
I would like to change to the "implicit" grant. My authorisation server and resource server are running in the same application.
This is my current configuration of the authorisation server:
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private MyApplicationSecuritySettings securitySettings;
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private DataSource dataSource;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(securitySettings.getAngularClientId())
.authorizedGrantTypes("password","refresh_token")
.scopes("read", "write")
.resourceIds(RESOURCE_ID)
.secret(passwordEncoder.encode(securitySettings.getAngularClientSecret()));
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore())
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.passwordEncoder(passwordEncoder);
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(tokenStore());
return tokenServices;
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource) {
// Workaround for https://github.com/spring-projects/spring-boot/issues/5071
#Override
protected OAuth2Authentication deserializeAuthentication(byte[] authentication) {
ObjectInputStream input = null;
try {
input = new ConfigurableObjectInputStream(
new ByteArrayInputStream(authentication),
Thread.currentThread().getContextClassLoader());
return (OAuth2Authentication) input.readObject();
} catch (Exception ex) {
throw new IllegalArgumentException(ex);
} finally {
if (input != null) {
try {
input.close();
} catch (IOException ex) {
// Continue
}
}
}
}
};
}
}
I also have this WebSecurityConfigurerAdapter subclass:
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private PasswordEncoder passwordEncoder;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
#Override
public void configure(WebSecurity web) throws Exception {
// Disable security for bower components
web.ignoring().antMatchers("/components/**");
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
and this is the configuration of the resource server:
#Configuration
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin", "/admin/", "/index.html", "/home.html", "/", "/logout",
"/partials/login.html"
)
.permitAll()
.authorizeRequests()
.antMatchers("/management/**").hasRole("ADMIN")
.anyRequest().authenticated();
}
}
With this configuration, a token can be requested via /oauth/token. This is implemented in AngularJS when the user visits http://localhost:8080/admin/#/login and types his username and password in that HTML form.
For the implicit grant, I am changing the ClientDetailsServiceConfigurer to:
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(securitySettings.getAngularClientId())
.authorizedGrantTypes("implicit")
.scopes("read", "write")
.resourceIds(RESOURCE_ID);
}
If I then try the following url: http://localhost:8080/oauth/authorize?client_id=angularClient&response_type=token
The browser redirects to http://localhost:8080/login and shows:
<oauth>
<error_description>Full authentication is required to access this resource</error_description>
<error>unauthorized</error>
</oauth>
Am I testing this implicit flow wrongly?
Do I need to change something in the configuration so that the redirect goes to http://localhost:8080/admin/#/login instead? Or does the angular app need to go from http://localhost:8080/admin/#/login to http://localhost:8080/oauth/authorize passing in the user's username and password somewhere?

Categories

Resources