I have oauth2 + jwt authorization in my project.
#Component
#RequiredArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling().disable()
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
}
}
#Slf4j
#Configuration
public class JwtConfiguration {
#Value("${app.security.jwt.keystore-location}")
private String keyStorePath;
#Value("${app.security.jwt.keystore-password}")
private String keyStorePassword;
#Value("${app.security.jwt.key-alias}")
private String keyAlias;
#Bean
public KeyStore keyStore() {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(keyStorePath);
keyStore.load(resourceAsStream, keyStorePassword.toCharArray());
return keyStore;
} catch (IOException | CertificateException | NoSuchAlgorithmException | KeyStoreException e) {
log.error("Unable to load keystore: {}", keyStorePath, e);
}
throw new IllegalArgumentException("Unable to load keystore");
}
#Bean
public RSAPublicKey jwtValidationKey(KeyStore keyStore) {
try {
Certificate certificate = keyStore.getCertificate(keyAlias);
PublicKey publicKey = certificate.getPublicKey();
if (publicKey instanceof RSAPublicKey) {
return (RSAPublicKey) publicKey;
}
} catch (KeyStoreException e) {
log.error("Unable to load private key from keystore: {}", keyStorePath, e);
}
throw new IllegalArgumentException("Unable to load RSA public key");
}
#Bean
public JwtDecoder jwtDecoder(RSAPublicKey rsaPublicKey) {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey(rsaPublicKey).build();
OAuth2TokenValidator<Jwt> validator = new Validator();
jwtDecoder.setJwtValidator(validator);
return jwtDecoder;
}
class Validator implements OAuth2TokenValidator<Jwt> {
OAuth2Error error = new OAuth2Error("error", "error description", null);
#Override
public OAuth2TokenValidatorResult validate(Jwt jwt) {
......
return OAuth2TokenValidatorResult.success();
}
}
}
i followed example(https://medium.com/swlh/stateless-jwt-authentication-with-spring-boot-a-better-approach-1f5dbae6c30f) and used jks for keys, in this case everything works. In my case, this approach of using jks is not suitable and i need to use kid.crt. The most interesting thing is that kid is the name of the file and it matches the kid field in the jwt header. That is, having received the kid field from the header, we should get a file that looks like kid.crt. I don't know how to get rid of jks in favor crt. How to create such a .crt? and how to configure at what point to take the file with the key?
jks I created this way
keytool -genkey -alias jwtsigning -keyalg RSA -keystore keystore.jks -keysize 2048
my application.properties
app.security.jwt.keystore-password=password
app.security.jwt.key-alias=jwtsigning
app.security.jwt.keystore-location=keys/keystore.jks
dependecies
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.5.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
The key that you need to validate an incoming JWT should come from the Authorization Server which issued you the JWT. You don't create it yourself, unless you are also in control of the Authorization Server, then you had to create a pair of private and public key. The private key is used to sign JWTs, and the public key should be distributed to APIs, so they can validate JWTs.
A perfect way is when the Authorization Server exposes a JWKS endpoint where your API can download the relevant key from. If this isn't possible in your case and you really need the key file, then you should get it from whoever manages the Authorization Server. You can then have a look e.g. here: Adding .crt to Spring Boot to enable SSL on how to add a crt into a keystore. Once you have the keystore, the code you have should work.
Related
I want to set up a very basic REST API using Spark-java, which just checks an access token obtained from my own authorisation server. It creates a GET request to the authorisation server's /oauth/authorize endpoint followed by ?token=$ACCESS_TOKEN.
Whenever I try this, I get diverted to the /error endpoint and a 403 error.
Here's my API class:
import org.apache.http.client.methods.HttpGet;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import spark.utils.StringUtils;
import java.io.IOException;
import static spark.Spark.*;
public class SampleAPI {
private static final Logger logger = LoggerFactory.getLogger("SampleAPI");
public static void main(String[] args) {
// Run on port 9782
port(9782);
// Just returns "Hello there" to the client's console
before((preRequest, preResponse) -> {
System.out.println("Getting token from request");
final String authHeader = preRequest.headers("Authorization");
//todo validate token, don't just accept it because it's not null/empty
if(StringUtils.isEmpty(authHeader) || !isAuthenticated(authHeader)){
halt(401, "Access not authorised");
} else {
System.out.println("Token = " + authHeader);
}
});
get("/", (res, req) -> "Hello there");
}
private static boolean isAuthenticated(final String authHeader) {
String url = "http://localhost:9780/oauth/authorize";
//"Bearer " is before the actual token in authHeader, so we need to extract the token itself as a substring
String token = authHeader.substring(7);
HttpGet getAuthRequest = new HttpGet(url + "?token=" + token);
getAuthRequest.setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType());
CloseableHttpClient httpClient = HttpClients.createMinimal();
try {
CloseableHttpResponse response = httpClient.execute(getAuthRequest);
int statusCode = response.getStatusLine().getStatusCode();
System.out.println("Status code " + statusCode + " returned for access token " + authHeader);
return statusCode == 200;
} catch (IOException ioException) {
System.out.println("Exception when trying to validate access token " + ioException);
}
return false;
}
}
The System.out.println statements are just for debugging.
Here's my authorisation server's WebSecurityConfigurerAdapter class:
package main.config;
import main.service.ClientAppDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
#Configuration
#EnableWebSecurity(debug = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
//returns AuthenticationManager from the superclass for authenticating users
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder getPasswordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
#Override
public void configure(WebSecurity web) throws Exception {
//Allow for DB access without any credentials
web.ignoring().antMatchers("/h2-console/**");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//configures user details, and uses the custom UserDetailsService to check user credentials
auth.userDetailsService(userDetailsService).passwordEncoder(getPasswordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated();
//disable CORS and CSRF protection for Postman testing
http.cors().disable().anonymous().disable();
http.headers().frameOptions().disable();
http.csrf().disable();
}
}
And here's my authorisation server's application.properties:
server.port=9780
#in-memory database, will get populated using data.sql
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=admin
spring.datasource.password=syst3m
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.properties.hibernate.format_sql=true
#adds to existing DB instead of tearing it down and re-populating it every time the app is started
spring.jpa.hibernate.ddl-auto=update
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
spring.h2.console.settings.trace=false
spring.h2.console.settings.web-allow-others=false
What have I done wrong? Do I need to specify my API as a resource server using Spring Security? Do I need to add it to the authorisation server's application.properties?
If you want to use Spring as a security framework then the most common option is to configure it as a resource server. Here is a getting started tutorial. The API will then never get redirected.
With Spark another option is to just provide a basic filter that uses a JWT validation library, such as jose4j. This tends to provide better control over error responses and gives you better visibility over what is going on. See this Kotlin example, which will be easy enough to translate to Java.
Problem:
I am using MFP 7.1 and oauth.tai_1.0.0.jar in my android application for app authenticity and have defined the realm on MFP's end. Each time I try to register to the application I see in the log
OAuthTAI Authentication failed with Status = 401, WWW-Authenticate: Bearer realm="imfAuthentication", scope="UserAuthRealm"
This is not preventing the application flow. I am only getting this error in the log and this error is seen before the realm class's init method is initialized and after that everything works fine.
I am wondering why I am getting this error.
Analysis:
I have checked the challenge handler in android, it is fine. I also did a fresh installation of the app in order to be sure of a new access token being sent from MFP.
I had also checked in MFP' Oauth jar and checked the 401 error case, it checks for invalid_token and invalid_authorization. But in my case, none of these two are there as I am not getting this in error description. I have the custom authenticator class defined which is mapped to UserAuthReal, code below:
CustomUserAuthenticator.java
package com.ibm.mfp;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.worklight.core.auth.impl.AuthenticationContext;
import com.worklight.server.auth.api.AuthenticationResult;
import com.worklight.server.auth.api.AuthenticationStatus;
import com.worklight.server.auth.api.MissingConfigurationOptionException;
import com.worklight.server.auth.api.UserIdentity;
import com.worklight.server.auth.api.WorkLightAuthenticator;
public class CustomUserAuthenticator implements WorkLightAuthenticator {
private static final long serialVersionUID = -548850541866024092L;
private static final Logger logger = Logger.getLogger(CustomUserAuthenticator.class.getName());
private String pin;
private String userName;
private String uniqueID;
private String userNumber;
private String userAuthFlag;
private String registrationNumber;
protected Map<String, Object> authenticationData;
public void init(Map<String, String> options) throws MissingConfigurationOptionException {
logger.info("CustomUserAuthenticator initialized");
}
public AuthenticationResult processRequest(HttpServletRequest request, HttpServletResponse response,
boolean isAccessToProtectedResource) throws IOException, ServletException {
String clientID = AuthenticationContext.getCurrentClientId();
logger.info("CustomUserAuthenticator :: processRequest : clientID : " + clientID);
String requestURI = request.getRequestURI();
logger.info("CustomUserAuthenticator :: processRequest : request.getRequestURI() :" + requestURI);
String requestQueryString = request.getQueryString();
requestQueryString = null;
logger.info("CustomUserAuthenticator :: processRequest : request.getQueryString() :" + requestQueryString);
// Request the epin from the user
if (request.getRequestURI().contains("/ADIBMBA/auth/v2/auth")) {
this.pin = request.getParameter("pin");
this.userName= request.getParameter("userName");
this.uniqueID = request.getParameter("uniqueID");
this.userNumber = request.getParameter("userNumber");
this.userAuthFlag = request.getParameter("userAuthFlag");
this.registrationNumber = request.getParameter("registrationNumber");
if (null != this.customerNumber) {
logger.info(
"CustomUserAuthenticator :: processRequest : request.getRequestURI() : getParameter customerNumber : "
+ this.customerNumber);
}
if (null != pin && pin.length() > 0) {
return AuthenticationResult.createFrom(AuthenticationStatus.SUCCESS);
} else {
response.setContentType("application/json; charset=UTF-8");
response.setHeader("Cache-Control", "no-cache, must-revalidate");
response.getWriter().print("{\"authStatus\":\"required\", \"errorMessage\":\"Please enter epin\"}");
return AuthenticationResult.createFrom(AuthenticationStatus.CLIENT_INTERACTION_REQUIRED);
}
}
if (!isAccessToProtectedResource) {
return AuthenticationResult.createFrom(AuthenticationStatus.REQUEST_NOT_RECOGNIZED);
}
response.setContentType("application/json; charset=UTF-8");
response.setHeader("Cache-Control", "no-cache, must-revalidate");
response.getWriter().print("{\"authStatus\":\"required\"}");
return AuthenticationResult.createFrom(AuthenticationStatus.CLIENT_INTERACTION_REQUIRED);
}
public boolean changeResponseOnSuccess(HttpServletRequest request, HttpServletResponse response)
throws IOException {
String requestURI2 = request.getRequestURI();
logger.info("CustomUserAuthenticator :: changeResponseOnSuccess : request ");
logger.info("CustomUserAuthenticator :: changeResponseOnSuccess : response ");
// first worked partially with if
// (request.getRequestURI().contains("/ADIBMBA/auth/v2/auth")){
if (request.getRequestURI().contains("/ADIBMBA/mainapps/services/apis/App/iOSnative")
|| (request.getRequestURI().contains("/ADIBMBA/auth/v2/auth"))) {
response.setContentType("application/json; charset=UTF-8");
response.setHeader("Cache-Control", "no-cache, must-revalidate");
response.getWriter().print("{\"authStatus\":\"complete\"}");
return true;
}
return false;
}
public AuthenticationResult processAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
String errorMessage) throws IOException, ServletException {
logger.info("CustomUserAuthenticator :: processAuthenticationFailure");
response.setContentType("application/json; charset=UTF-8");
response.setHeader("Cache-Control", "no-cache, must-revalidate");
response.getWriter().print("{\"authStatus\":\"failed\", \"errorMessage\":" + errorMessage + ","
+ (String) authenticationData.get("error") + "}");
return AuthenticationResult.createFrom(AuthenticationStatus.CLIENT_INTERACTION_REQUIRED);
}
public AuthenticationResult processRequestAlreadyAuthenticated(HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
logger.info("CustomUserAuthenticator :: processRequestAlreadyAuthenticated");
return
AuthenticationResult.createFrom(AuthenticationStatus.
REQUEST_NOT_RECOGNIZED);
}
public Map<String, Object> getAuthenticationData() {
authenticationData = new HashMap<String, Object>();
authenticationData.put("userName", userName);
authenticationData.put("uniqueID", uniqueID);
authenticationData.put("pin", pin);
authenticationData.put("userNumber", userNumber);
authenticationData.put("userAuthFlag", userAuthFlag);
authenticationData.put("registrationNumber", registrationNumber);
return authenticationData;
}
public HttpServletRequest getRequestToProceed(HttpServletRequest request, HttpServletResponse response,
UserIdentity userIdentity) throws IOException {
return null;
}
#Override
public WorkLightAuthenticator clone() throws CloneNotSupportedException {
CustomUserAuthenticator otherAuthenticator = (CustomUserAuthenticator) super.clone();
return otherAuthenticator;
}
}
Summary:
If the application flow is normal then why i am getting this OAuthTAI 401 error in log. Suppose If it is a problem related to token & id token then i should not be able to access protected resource data. Application should not allow me to proceed further.
From the description and comments, it appears you have mixed up Liberty's OAuth TAI and MFP's OAuth security model.
MFP's OAuth security model is used to protect MFP resources ( adapters and runtime endpoints), while Liberty's OAuth TAI is to protect resources deployed on Liberty server ( eg: web applications).
The link you followed details steps using which MFP server can act as the OAuth server for resources deployed in Liberty server.
From the description and the custom Authenticator that extends WorklightAuthenticator you are depending on the security validation to be done by MFP's security framework. If the requirement is for your MFP adapters to be protected by OAuth security and the device begins by obtaining an OAuth token from MFP server, then you should use MFP's OAuth security and not resort to Liberty OAuth TAI. MFP's OAuth security framework works out of the box, without any need to configure TAI.
Refer to the following links for a better understanding and working samples:
a) MFP OAuth security model
b) Java adapters
c) Custom Authenticator
We are securing out REST services using spring security OAuth2. Applications can call into either the /oauth/authorize, /oauth/token or /rest-api endpoints. The token and rest-api endpoints are stateless and do not need a session.
Can we invalidate the session after the user is authenticated? If so, what is the best approach. We want the user to sign-in always whenever a call to /oauth/authorize is made. Currently, calls to /oauth/authorize are skipping authentication whenever a session exists.
Understanding that the question is a bit old, I hope that the following could be helpful for those who search for the correct answer for the question
OP asked not about tokens invalidation, but how to invalidate httpSession on Spring OAuth2 server right after user authentication successfully passed and a valid access_token or authorization_code (for subsequent getting of access_token) returned to a client.
There is no out-of-the-box solution for this use-case still. But working workaround from the most active contributor of spring-security-oauth, Dave Syer, could be found here on GitHub
Just copy of the code from there:
#Service
#Aspect
public class SessionInvalidationOauth2GrantAspect {
private static final String FORWARD_OAUTH_CONFIRM_ACCESS = "forward:/oauth/confirm_access";
private static final Logger logger = Logger.getLogger(SessionInvalidationOauth2GrantAspect.class);
#AfterReturning(value = "within(org.springframework.security.oauth2.provider.endpoint..*) && #annotation(org.springframework.web.bind.annotation.RequestMapping)", returning = "result")
public void authorizationAdvice(JoinPoint joinpoint, ModelAndView result) throws Throwable {
// If we're not going to the confirm_access page, it means approval has been skipped due to existing access
// token or something else and they'll be being sent back to app. Time to end session.
if (!FORWARD_OAUTH_CONFIRM_ACCESS.equals(result.getViewName())) {
invalidateSession();
}
}
#AfterReturning(value = "within(org.springframework.security.oauth2.provider.endpoint..*) && #annotation(org.springframework.web.bind.annotation.RequestMapping)", returning = "result")
public void authorizationAdvice(JoinPoint joinpoint, View result) throws Throwable {
// Anything returning a view and not a ModelView is going to be redirecting outside of the app (I think).
// This happens after the authorize approve / deny page with the POST to /oauth/authorize. This is the time
// to kill the session since they'll be being sent back to the requesting app.
invalidateSession();
}
#AfterThrowing(value = "within(org.springframework.security.oauth2.provider.endpoint..*) && #annotation(org.springframework.web.bind.annotation.RequestMapping)", throwing = "error")
public void authorizationErrorAdvice(JoinPoint joinpoint) throws Throwable {
invalidateSession();
}
private void invalidateSession() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
HttpSession session = request.getSession(false);
if (session != null) {
logger.warn(String.format("As part of OAuth application grant processing, invalidating session for request %s", request.getRequestURI()));
session.invalidate();
SecurityContextHolder.clearContext();
}
}
}
add pom.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
Another solution could be to set session time out to some very small value. The simplest way to achieve that is put the following to application.yml config:
server:
session:
timeout: 1
But it's not ideal solution as the minimum value could be provider is 1 (zero is reserved for infinite sessions) and it is in minutes not in seconds
From what I understand, you are trying to programmatically logout after you have undertaken certain set of actions. Probably you should look into the SecurityContextLogoutHandler and see how it works. There is a method for logout there. I think calling it as an advice will solve your problem.
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
Assert.notNull(request, "HttpServletRequest required");
if (invalidateHttpSession) {
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
}
SecurityContextHolder.clearContext();
}
First: in your configuration declare bean with token store for oauth
#Bean
#Primary
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
For controller approach we made the following class
#Controller
public class TokenController {
#RequestMapping(value = "/oauth/token/revoke", method = RequestMethod.POST)
public #ResponseBody void create(#RequestParam("token") String value) {
this.revokeToken(value);
}
#Autowired
TokenStore tokenStore;
public boolean revokeToken(String tokenValue) {
OAuth2AccessToken accessToken = tokenStore.readAccessToken(tokenValue);
if (accessToken == null) {
return false;
}
if (accessToken.getRefreshToken() != null) {
tokenStore.removeRefreshToken(accessToken.getRefreshToken());
}
tokenStore.removeAccessToken(accessToken);
return true;
}
}
If you don't wan't to use this approach you can grab current user's token autowiring Principal:
OAuth2Authentication authorization = (OAuth2Authentication) principal;
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authorization.getDetails();
String token = details.getTokenValue();
Or even autowiring OAuth2Authentication:
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
String token = details.getTokenValue();
I can offer such an option (according to #de_xtr recomendation):
import static org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes;
#Slf4j
#Component
#Aspect
public class InvalidateSessionAspect {
private final LogoutHandler logoutHandler;
public InvalidateSessionAspect() {
logoutHandler = new SecurityContextLogoutHandler();
}
#Pointcut("execution(* org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(..))")
public void postAccessTokenPointcut() {
}
#AfterReturning(value = "postAccessTokenPointcut()", returning = "entity")
public void invalidateSession(JoinPoint jp, Object entity) {
log.debug("[d] Trying to invalidate the session...");
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) currentRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
logoutHandler.logout(request, null, null);
log.debug("[d] Session has been invalidated");
}
}
And the option without any aspects:
#Slf4j
class LogoutHandlerInterceptor implements HandlerInterceptor {
#Override
public void postHandle(HttpServletRequest req, HttpServletResponse resp, Object h, ModelAndView view) {
HttpSession session = req.getSession(false);
if (session != null) {
log.debug("[d] Trying to invalidate the session...");
session.invalidate();
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(null);
SecurityContextHolder.clearContext();
log.debug("[d] Session has been invalidated");
}
}
}
#Configuration
#EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
//...
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.addInterceptor(new LogoutHandlerInterceptor())
// ...
;
}
}
I am getting the error:
"error": "invalid_grant",
"error_description": "Bad credentials"
Here is the request which I make:
POST /oauth/token HTTP/1.1
Host: localhost:8443
Authorization: Basic bW9iaWxlOg==
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded
username=admin&password=pass&client_id=mobile&grant_type=password&client_secret=
My code is from here: https://github.com/juleswhite/mobilecloud-14/tree/master/examples/9-VideoServiceWithOauth2
Here is the code:
Application.java:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import com.capstone.auth.OAuth2SecurityConfiguration;
import com.google.common.io.BaseEncoding;
#Configuration
#EnableAutoConfiguration
#ComponentScan
#EnableWebMvc
#Import(OAuth2SecurityConfiguration.class)
public class Application extends RepositoryRestMvcConfiguration{
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(Application.class, args);
}
}
ClientAndUserDetailsService.java
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.ClientRegistrationException;
import org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService;
/**
* A class that combines a UserDetailsService and ClientDetailsService
* into a single object.
*
* #author jules
*
*/
public class ClientAndUserDetailsService implements UserDetailsService,
ClientDetailsService {
private final ClientDetailsService clients_;
private final UserDetailsService users_;
private final ClientDetailsUserDetailsService clientDetailsWrapper_;
public ClientAndUserDetailsService(ClientDetailsService clients,
UserDetailsService users) {
super();
clients_ = clients;
users_ = users;
clientDetailsWrapper_ = new ClientDetailsUserDetailsService(clients_);
}
#Override
public ClientDetails loadClientByClientId(String clientId)
throws ClientRegistrationException {
return clients_.loadClientByClientId(clientId);
}
#Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
UserDetails user = null;
try{
user = users_.loadUserByUsername(username);
}catch(UsernameNotFoundException e){
user = clientDetailsWrapper_.loadUserByUsername(username);
}
return user;
}
}
OAuth2SecurityConfiguration.java
#Configuration
public class OAuth2SecurityConfiguration {
// This first section of the configuration just makes sure that Spring
// Security picks
// up the UserDetailsService that we create below.
#Configuration
#EnableWebSecurity
protected static class WebSecurityConfiguration extends
WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
protected void registerAuthentication(
final AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}
/**
* This method is used to configure who is allowed to access which parts of
* our resource server (i.e. the "/video" endpoint)
*/
#Configuration
#EnableResourceServer
protected static class ResourceServer extends
ResourceServerConfigurerAdapter {
// This method configures the OAuth scopes required by clients to access
// all of the paths in the video service.
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().antMatchers("/oauth/token").anonymous();
// If you were going to reuse this class in another
// application, this is one of the key sections that you
// would want to change
// Require all GET requests to have client "read" scope
http.authorizeRequests().antMatchers(HttpMethod.GET, "/**")
.access("#oauth2.hasScope('read')");
// Require all other requests to have "write" scope
http.authorizeRequests().antMatchers("/**")
.access("#oauth2.hasScope('write')");
}
}
/**
* This class is used to configure how our authorization server (the
* "/oauth/token" endpoint) validates client credentials.
*/
#Configuration
#EnableAuthorizationServer
#Order(Ordered.LOWEST_PRECEDENCE - 100)
protected static class OAuth2Config extends
AuthorizationServerConfigurerAdapter {
// Delegate the processing of Authentication requests to the framework
#Autowired
private AuthenticationManager authenticationManager;
// A data structure used to store both a ClientDetailsService and a
// UserDetailsService
private ClientAndUserDetailsService combinedService_;
/**
*
* This constructor is used to setup the clients and users that will be
* able to login to the system. This is a VERY insecure setup that is
* using hard-coded lists of clients / users / passwords and should
* never be used for anything other than local testing on a machine that
* is not accessible via the Internet. Even if you use this code for
* testing, at the bare minimum, you should consider changing the
* passwords listed below and updating the VideoSvcClientApiTest.
*
* #param auth
* #throws Exception
*/
public OAuth2Config() throws Exception {
// If you were going to reuse this class in another
// application, this is one of the key sections that you
// would want to change
// Create a service that has the credentials for all our clients
ClientDetailsService csvc = new InMemoryClientDetailsServiceBuilder()
// Create a client that has "read" and "write" access to the
// video service
.withClient("mobile")
.authorizedGrantTypes("password")
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
.scopes("read", "write")
.resourceIds("test")
.and()
// Create a second client that only has "read" access to the
// video service
.withClient("mobileReader")
.authorizedGrantTypes("password")
.authorities("ROLE_CLIENT").scopes("read")
.resourceIds("test").accessTokenValiditySeconds(3600)
.and().build();
// Create a series of hard-coded users.
UserDetailsService svc = new InMemoryUserDetailsManager(
Arrays.asList(
User.create("admin", "pass", "ADMIN", "USER"),
User.create("user0", "pass", "USER"),
User.create("username", "password", "USER")));
// Since clients have to use BASIC authentication with the client's
// id/secret,
// when sending a request for a password grant, we make each client
// a user
// as well. When the BASIC authentication information is pulled from
// the
// request, this combined UserDetailsService will authenticate that
// the
// client is a valid "user".
combinedService_ = new ClientAndUserDetailsService(csvc, svc);
}
/**
* Return the list of trusted client information to anyone who asks for
* it.
*/
#Bean
public ClientDetailsService clientDetailsService() throws Exception {
return combinedService_;
}
/**
* Return all of our user information to anyone in the framework who
* requests it.
*/
#Bean
public UserDetailsService userDetailsService() {
return combinedService_;
}
/**
* This method tells our AuthorizationServerConfigurerAdapter to use the
* delegated AuthenticationManager to process authentication requests.
*/
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.authenticationManager(authenticationManager);
}
/**
* This method tells the AuthorizationServerConfigurerAdapter to use our
* self-defined client details service to authenticate clients with.
*/
#Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.withClientDetails(clientDetailsService());
}
}
// This version uses the Tomcat web container and configures it to
// support HTTPS. The code below performs the configuration of Tomcat
// for HTTPS. Each web container has a different API for configuring
// HTTPS.
//
// The app now requires that you pass the location of the keystore and
// the password for your private key that you would like to setup HTTPS
// with. In Eclipse, you can set these options by going to:
// 1. Run->Run Configurations
// 2. Under Java Applications, select your run configuration for this app
// 3. Open the Arguments tab
// 4. In VM Arguments, provide the following information to use the
// default keystore provided with the sample code:
//
// -Dkeystore.file=src/main/resources/private/keystore
// -Dkeystore.pass=changeit
//
// 5. Note, this keystore is highly insecure! If you want more securtiy, you
// should obtain a real SSL certificate:
//
// http://tomcat.apache.org/tomcat-7.0-doc/ssl-howto.html
//
#Bean
EmbeddedServletContainerCustomizer containerCustomizer(
#Value("${keystore.file:src/main/resources/private/keystore}") String keystoreFile,
#Value("${keystore.pass:changeit}") final String keystorePass)
throws Exception {
// If you were going to reuse this class in another
// application, this is one of the key sections that you
// would want to change
final String absoluteKeystoreFile = new File(keystoreFile)
.getAbsolutePath();
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
TomcatEmbeddedServletContainerFactory tomcat = (TomcatEmbeddedServletContainerFactory) container;
tomcat.addConnectorCustomizers(new TomcatConnectorCustomizer() {
#Override
public void customize(Connector connector) {
connector.setPort(8443);
connector.setSecure(true);
connector.setScheme("https");
Http11NioProtocol proto = (Http11NioProtocol) connector
.getProtocolHandler();
proto.setSSLEnabled(true);
proto.setKeystoreFile(absoluteKeystoreFile);
proto.setKeystorePass(keystorePass);
proto.setKeystoreType("JKS");
proto.setKeyAlias("tomcat");
}
});
}
};
}
}
Thanks for your attention and time!
Short answer: you can't #Autowired an AuthenticationManager into a AuthorizationServerConfigurerAdapter if you are using Spring Boot (yet).
Long anwswer: this sample works though because it autowires the AuthenticationManagerBuilder instead, and constructs a lazy-init version of the AuthenticationManager for the token granter to use. With Spring OAuth2 2.0.3 you will have to create that lazy AuthenticationManager yourself (like this:
authenticationManager = new AuthenticationManager() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
return auth.getOrBuild().authenticate(authentication);
}
};
With snapshots (or 2.0.4 when it is released) you can just use the new overloaded method in AuthorizationServerConfigurerAdapter.
Hi I am working on android app in which I have integrated bigquery. I see sometimes we are getting a lot of SSL exceptions while inserting data to big query tables. I don't know how to handle this . Please help what exactly is the cause of this problem. Here is the same thread but no answer Bigquery SSL error while doing streaming insert api call
javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:946) ~[na:1.7.0_51]
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1312) ~[na:1.7.0_51]
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1339) ~[na:1.7.0_51]
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1323) ~[na:1.7.0_51]
at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:563) ~[na:1.7.0_51]
at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185) ~[na:1.7.0_51]
at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1091) ~[na:1.7.0_51]
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:250) ~[na:1.7.0_51]
at com.google.api.client.http.javanet.NetHttpRequest.execute(NetHttpRequest.java:77) ~[google-http-client-1.19.0.jar:1.19.0]
at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:965) ~[google-http-client-1.19.0.jar:1.19.0]
at com.google.api.client.googleapis.batch.BatchRequest.execute(BatchRequest.java:241) ~[google-api-client-1.19.1.jar:1.19.1]
at com.livestream.analytics.datastorage.worker.InsertApiActor$$anonfun$2$$anonfun$4.apply(InsertApiActor.scala:131) ~[analytics-data-storage-worker_2.11-1.0.0.jar:1.0.0]
at com.livestream.analytics.datastorage.worker.InsertApiActor$$anonfun$2$$anonfun$4.apply(InsertApiActor.scala:118) ~[analytics-data-storage-worker_2.11-1.0.0.jar:1.0.0]
at com.livestream.analytics.common.store.bigquery.api.BigQueryApi$.withSyncClient(BigQueryApi.scala:71) ~[analytics-common_2.11-1.0.0.jar:1.0.0]
at com.livestream.analytics.datastorage.worker.InsertApiActor$$anonfun$2.apply$mcV$sp(InsertApiActor.scala:118) ~[analytics-data-storage-worker_2.11-1.0.0.jar:1.0.0]
at com.livestream.analytics.datastorage.worker.InsertApiActor$$anonfun$2.apply(InsertApiActor.scala:115) ~[analytics-data-storage-worker_2.11-1.0.0.jar:1.0.0]
at com.livestream.analytics.datastorage.worker.InsertApiActor$$anonfun$2.apply(InsertApiActor.scala:115) ~[analytics-data-storage-worker_2.11-1.0.0.jar:1.0.0]
at com.livestream.analytics.common.monitoring.Timer.time(Timer.scala:15) ~[analytics-common_2.11-1.0.0.jar:1.0.0]
at com.livestream.analytics.datastorage.worker.InsertApiActor.com$livestream$analytics$datastorage$worker$InsertApiActor$$insertDataRowsToBigQueryTable(InsertApiActor.scala:115) [analytics-data-storage-worker_2.11-1.0.0.jar:1.0.0]
at com.livestream.analytics.datastorage.worker.InsertApiActor$$anonfun$receive$1.applyOrElse(InsertApiActor.scala:80) [analytics-data-storage-worker_2.11-1.0.0.jar:1.0.0]
at akka.actor.Actor$class.aroundReceive(Actor.scala:465) [akka-actor_2.11-2.3.9.jar:na]
at com.livestream.analytics.datastorage.worker.InsertApiActor.aroundReceive(InsertApiActor.scala:54) [analytics-data-storage-worker_2.11-1.0.0.jar:1.0.0]
at akka.actor.ActorCell.receiveMessage(ActorCell.scala:516) [akka-actor_2.11-2.3.9.jar:na]
at akka.actor.ActorCell.invoke(ActorCell.scala:487) [akka-actor_2.11-2.3.9.jar:na]
at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:254) [akka-actor_2.11-2.3.9.jar:na]
at akka.dispatch.Mailbox.run(Mailbox.scala:221) [akka-actor_2.11-2.3.9.jar:na]
at akka.dispatch.Mailbox.exec(Mailbox.scala:231) [akka-actor_2.11-2.3.9.jar:na]
at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260) [scala-library-2.11.5.jar:na]
at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339) [scala-library-2.11.5.jar:na]
at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979) [scala-library-2.11.5.jar:na]
at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107) [scala-library-2.11.5.jar:na]
Caused by: java.io.EOFException: SSL peer shut down incorrectly
at sun.security.ssl.InputRecord.read(InputRecord.java:482) ~[na:1.7.0_51]
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:927) ~[na:1.7.0_51]
... 30 common frames omitted
Probably the server is asking for a client certificate and you aren't providing one. The server will provide a list of trusted signers, and your client certificate needs to be signed by one of those. You can't use a self-signed certificate for the client unless you've made special arrangements with the server, i.e. imported your client certificate into its trusted certificate list. Your SSL client won't send a certificate if it can't find one, or if the one(s) that it finds don't have trusted signers.
It doesn't have anything to do with what the SSL connection was going to do after it was established, e.g. SQL queries, updates, etc.
If you are using (https) requests you need to add Security Certificate in your app. here is link how to add it.
http://stackoverflow.com/questions/4065379/how-to-create-a-bks-bouncycastle-format-java-keystore-that-contains-a-client-c
You can test if indeed security certificate is causing it, Add this class in your project.
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class HttpsTrustManager implements X509TrustManager {
private static TrustManager[] trustManagers;
private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[]{};
#Override
public void checkClientTrusted(
java.security.cert.X509Certificate[] x509Certificates, String s)
throws java.security.cert.CertificateException {
}
#Override
public void checkServerTrusted(
java.security.cert.X509Certificate[] x509Certificates, String s)
throws java.security.cert.CertificateException {
}
public boolean isClientTrusted(X509Certificate[] chain) {
return true;
}
public boolean isServerTrusted(X509Certificate[] chain) {
return true;
}
#Override
public X509Certificate[] getAcceptedIssuers() {
return _AcceptedIssuers;
}
public static void allowAllSSL() {
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
SSLContext context = null;
if (trustManagers == null) {
trustManagers = new TrustManager[]{new HttpsTrustManager()};
}
try {
context = SSLContext.getInstance("TLS");
context.init(null, trustManagers, new SecureRandom());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
HttpsURLConnection.setDefaultSSLSocketFactory(context
.getSocketFactory());
}
}
and call
HttpsTrustManager.allowAllSSL(); // Allow all SSL connections
before an API call.
NOTE:
This code skips verification and allows any certificate to work.
This method should not be used for secure communication.
This is just to check if Certificate authentication is causing the error.