I am currently using Spring Security with CAS as the authentication mechanism to secure a web app as well as my RESTful services API (on a separate server). I would like to make calls to the RESTful services from my web app within AJAX. I have successfully setup a CAS proxy from the web app to the services. What's the best way of calling the services with the PGT inside of my AJAX/JQuery code to retrieve the services data?
Right now I can successfully read services data using the following test servlet, but am wondering what approach to use with AJAX.
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String targetUrl = "https://example.com/services/helloworld";
final CasAuthenticationToken token = (CasAuthenticationToken) req
.getUserPrincipal();
final String proxyTicket = token.getAssertion().getPrincipal()
.getProxyTicketFor(targetUrl);
// Make a remote call using the proxy ticket
final String serviceUrl = targetUrl + "?ticket="
+ URLEncoder.encode(proxyTicket, "UTF-8");
String proxyResponse = CommonUtils.getResponseFromServer(serviceUrl);
resp.setStatus(HttpServletResponse.SC_OK);
resp.setContentType("text/plain");
PrintWriter writer = resp.getWriter();
writer.println(proxyResponse);
writer.flush();
}
I ended up throwing in the towel on this and moving to an OAuth 2 solution.
Related
Is this possible to manage multiple login page with spring authorization server?
Suppose, we have 2 client and both client want a different login page
client 1 need /login url
client 2 need /login2 url..
I believe the question is how to brand a login page based on the current clientId. We can use any technique available in Spring Security, as it is fully available and not hidden when using Spring Authorization Server.
As you point out, one way to handle this would be to perform a custom redirect when authentication is required. This would be handled in a custom AuthenticationEntryPoint. You can build a delegate with a mapping of clientIds to login urls. Normally, I'd encourage you to try it yourself (learning new things is fun, right!?), but in this case, here's an example:
public class BrandedAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final Map<String, AuthenticationEntryPoint> authenticationEntryPoints;
private final AuthenticationEntryPoint defaultEntryPoint = new LoginUrlAuthenticationEntryPoint("/login");
public BrandedAuthenticationEntryPoint(Map<String, String> loginUrls) {
Map<String, AuthenticationEntryPoint> map = new HashMap<>();
loginUrls.forEach((clientId, loginUrl) ->
map.put(clientId, new LoginUrlAuthenticationEntryPoint(loginUrl)));
this.authenticationEntryPoints = Collections.unmodifiableMap(map);
}
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
AuthenticationEntryPoint delegate = this.defaultEntryPoint;
// Attempt to resolve a specific login url based on clientId
String clientId = request.getParameter("clientId");
if (clientId != null) {
delegate = this.authenticationEntryPoints.getOrDefault(clientId, this.defaultEntryPoint);
}
delegate.commence(request, response, authException);
}
}
SAS and Form Login are two different filter chains in the default sample, so you would apply this in the normal way on both filter chains:
http.exceptionHandling(exceptionHandling -> exceptionHandling
.authenticationEntryPoint(new BrandedAuthenticationEntryPoint(...))
);
More information on AuthenticationEntryPoint is available in the reference docs.
i am using java library client for web application authentication, i produce authorization url using client secret and client id,also i provided a redirect url within google api console,but i don't know if it is necessary for me to create this server to receive refresh token?
i mean in production i should provide a separate server to receive the refresh token?(redirect url comes to this server)
the main problem is user should paste the produced url on browser by himself but i want to open browser authmaticly , the second one is about reciving the refresh token i am not sure about creating another server to recieve refreshcode and i can't use service accounts i am going with web flow authentication.
UserAuthorizer userAuthorizer =
UserAuthorizer.newBuilder()
.setClientId(ClientId.of(clientId, clientSecret))
.setScopes(SCOPES)
.setCallbackUri(URI.create(OAUTH2_CALLBACK_URL_CONFIGURED_AT_GOOGLE_CONSOLE))
.build();
baseUri = URI.create("http://localhost:" + simpleCallbackServer.getLocalPort());
System.out.printf(
"Paste this url in your browser:%n%s%n",
userAuthorizer.getAuthorizationUrl(loginEmailAddressHint, state, baseUri));
and this is local server to receive refresh token:
private static class SimpleCallbackServer extends ServerSocket {
private AuthorizationResponse authorizationResponse;
SimpleCallbackServer() throws IOException {
// Passes a port # of zero so that a port will be automatically allocated.
super(0);
}
/**
* Blocks until a connection is made to this server. After this method completes, the
* authorizationResponse of this server will be set, provided the request line is in the
* expected format.
*/
#Override
public Socket accept() throws IOException {
Socket socket = super.accept();
}
}
for those who struggling to get authorized using google oauth2.0 with spring boot
you cant redirect user to authorization url(which google authorization server gives to using your client id and client secret) use a controller to redirect user:
#GetMapping(value = "/redirect-user")
public ResponseEntity<Object> redirectToExternalUrl() throws URISyntaxException {
String url=gs.createUserAuthorizationUrl();
URI authorizationUrl = new URI(url);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setLocation(authorizationUrl);
return new ResponseEntity<>(httpHeaders, HttpStatus.FOUND);
}
at service layer createUserAuthorizationUrl() method is like below:
public String createUserAuthorizationUrl() {
clientId = "client-id";
clientSecret = "client-secret-code";
userAuthorizer =
UserAuthorizer.newBuilder()
.setClientId(ClientId.of(clientId, clientSecret))
.setScopes(SCOPES)
.setCallbackUri(URI.create("/oauth2callback"))
.build();
baseUri = URI.create("your-app-redirect-url-configured-at-google-console" + "your-spring-boot-server-port"); //giving redirect url
String redirectURL = userAuthorizer.getAuthorizationUrl(loginEmailAddressHint, state, baseUri).toString();
return redirectURL;
}
and let's create a controller to support the get request comming from google authorization server with an code. we are going to use that code to get access token from google.i get state and code by #RequestParam
and i also want to redirect user to my application.
#GetMapping(value = "/oauth2callback")
public ResponseEntity<Object> proceedeTOServer(#RequestParam String state,
#RequestParam String code) throws URISyntaxException {
String url="my-application-url-to-redirect-user";
URI dashboardURL = new URI(url);
HttpHeaders httpHeaders=new HttpHeaders();
httpHeaders.setLocation(dashboardURL);
gs.getCode(state,code);
return new ResponseEntity<>(httpHeaders,HttpStatus.FOUND);
}
and in getCode(code) in service layer i am going to send to code and receive the refresh token or access token:
UserCredentials userCredentials =userAuthorizer.getCredentialsFromCode(code, "your-app-redirect-url-configured-at-google-console" + "your-spring-boot-server-port");
For the past days I have been trying to figuring out how to make OAuth2 work on a native app with the OAuth2 client consisting of a separate frontend application with a Spring backend. Good news! I figured out a way to make it work both as web app (on a browser) as on a native (mobile) app. Here I would like to share my findings and ask for any suggestions on possible improvements.
Where Spring works out of the box
Spring Oauth2 works out of the box for web apps. We add the dependency <artifactId>spring-security-oauth2-autoconfigure</artifactId>. We add the annotation #EnableOAuth2Client. Furthermore, we add the configuration. For an in detail tutorial I would like to refer you to this tutorial.
Where challenges start to arise
Spring works with a session cookie (JSESSIONID) to establish a session which is send to the frontend using a Set-Cookie header. In a mobile application this Set-Cookie header is not send back to the backend on subsequent requests. This means that on a mobile application the backend sees each request as a new session. To solve this, I implement a session header rather than a cookie. This header can be read and therefore added to the subsequent requests.
#Bean
public HttpSessionIdResolver httpSessionIdResolver() {
return HeaderHttpSessionIdResolver.xAuthToken();
}
However, that solves only part of the problem. The frontend makes a request using window.location.href which makes it impossible to add custom headers (REST call cannot be used because it would make it impossible to redirect the caller to the authorization server login page, because the browser blocks this). The browser automatically adds cookies to calls made using window.location.href. That's why it works on browser, but not on a mobile application. Therefore, we need to modify Spring's OAuth2 process to be able to receive REST calls rather than a call using window.location.href.
The OAuth2 Client process in Spring
Following the Oauth2 process the frontend makes two calls to the backend:
Using window.location.href a call to be redirected to the Authorization server (e.g. Facebook, Google or your own authorization server).
Making a REST GET request with the code and state query parameter to retrieve an access token.
However, if Spring does not recognise the session (like on mobile phone) it creates a new OAuth2ClientContext class and therefore throws an error on the second call: InvalidRequestException("Possible CSRF detected - state parameter was required but no state could be found"); by the AuthorizationCodeAccessTokenProvider.class. The reason it throws this error is because the preservedState property is null on the request. This is nicely explained by this post's answer of #Nico de wit.
I created a visual of the Spring OAuth2 process which shows the box 'Context present in session?'. This is where it goes wrong as soon as you have retrieved the authorization code from logging into the authorization server. This is because further on in in the getParametersForToken box it checks the preservedState which is then null because it came from a new OAuth2ClientContext object (rather than the same object that was used when redirecting the first call to the page of the authorization server).
The solution
I solved this problem by extending OAuth2ClientContextFilter.class. This class is responsible for redirecting the user to the authorization server login page if no authorization code has been retrieved yet. Instead of redirecting, the custom class now sends back a 200 and the in the body an url to which the frontend needs to be redirected. Also the frontend can now make a REST call rather than using window.location.href to be redirected. That looks something like:
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException,
ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
request.setAttribute(CURRENT_URI, this.calculateCurrentUri(request));
try {
chain.doFilter(servletRequest, servletResponse);
} catch (IOException var9) {
throw var9;
} catch (Exception var10) {
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(var10);
UserRedirectRequiredException redirect = (UserRedirectRequiredException)this.throwableAnalyzer.getFirstThrowableOfType(UserRedirectRequiredException.class, causeChain);
if (redirect == null) {
if (var10 instanceof ServletException) {
throw (ServletException)var10;
}
if (var10 instanceof RuntimeException) {
throw (RuntimeException)var10;
}
throw new NestedServletException("Unhandled exception", var10);
}
// The original code redirects the caller to the authorization page
// this.redirectUser(redirect, request, response);
// Instead we create the redirect Url from the Exception and add it to the body
String redirectUrl = createRedirectUrl(redirect);
response.setStatus(200);
response.getWriter().write(redirectUrlToJson(redirectUrl));
}
}
The createRedirectUrl contains some logic building the Url:
private String createRedirectUrl(UserRedirectRequiredException e) {
String redirectUri = e.getRedirectUri();
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(redirectUri);
Map<String, String> requestParams = e.getRequestParams();
Iterator it = requestParams.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> param = (Map.Entry)it.next();
builder.queryParam(param.getKey(), param.getValue());
}
if (e.getStateKey() != null) {
builder.queryParam("state", e.getStateKey());
}
return builder.build().encode().toUriString();
}
I hope it helps others in the future by implementing OAuth2 using Spring on web and mobile applications. Feel free to give feedback!
Regards,
Bart
For an application I need to create a security façade in Spring 4.x.
This thiny layer must accepts any request from our mobile application and execute a security check for the provided token (with openId and Oauth).
Upon a successful validation, the request needs to be forwarded to the backend application, which does not need to be aware of the security token mechanism.
Thus, the flow will be something like this:
security_facade_url/path/of/the/request
With a header that indicates the backend to invoke upon successful validation of the token
Upon successful validation the security façade sends a request to the backend URL
backend_application_url/path/of/the/request
The façade must not have a controller which maps to any possible path of the request, but must call the request on the correct backend server, based on a value in the header of the request. Then return this response to the user.
What I have so far is an implementation of the HandlerInterceptor. This interceptor works, however, I am not really happy with the way I need to avoid the afterCompletion by throwing an exception in the postHandle method.
If I do not throw an error, the default error page is appended to the correct response in the afterCompletion step.
This is my code so far:
public class RequestProcessingInterceptor implements HandlerInterceptor {
private final Logger log = LoggerFactory.getLogger(RequestProcessingInterceptor.class);
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
log.info("Doing some security stuff now ...");
log.warn("... security ok ... since I am not really checking stuff");
return true;
}
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
log.info("Forwarding request and sending that info back ...");
ClientConfig config = new DefaultClientConfig();
Client client = Client.create(config);
WebResource service = client.resource(UriBuilder.fromUri("http://localhost:8080").build());
response.setContentType("application/json");
response.getWriter().write(service.path(modelAndView.getModel().get("path").toString()).accept("application/json").get(String.class));
response.setStatus(200);
throw new Exception("Need to avoid the execution of the afterCompletion. Only way to do so is by throwing an exception...");
}
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
Is there a more proper way to intervene with the Spring livecycle or obtain the behaviour as described above?
Found a better solution. For what I need, I do not need to manipulate the results in an interceptor.
A much cleaner way is to define a Controller which maps with the request methods.
#RequestMapping(method = {RequestMethod.GET, RequestMethod.PUT, RequestMethod.POST})
public void handleRequest(HttpServletRequest request, HttpServletResponse response) { // code omitted }
You should not try to avoid the call to afterCompletion. Just implement an empty method and let SpringFramework call it.
Provided your controller returns null indicating that no view has to be called, it should work with a smoother Spring integration.
But I cannot understand why you use Spring MVC here. As you only interact with low level HttpServletRequest and HttpServletResponse, you could as well use :
a dedicated servlet in charge to relay the request and response to the backend and write the returned value in the response
a filter that would do the security stuff before passing request to filter chain
I've been looking for a way to authenticate a user via REST controller (URL params).
The closest thing to do so is the following:
#Controller
#RequestMapping(value="/api/user")
public class UserController extends BaseJSONController{
static Logger sLogger = Logger.getLogger(UserController.class);
#RequestMapping(value = "/login", method = RequestMethod.POST)
public #ResponseBody String login(#RequestParam(value="username") String user, #RequestParam(value="password") String pass) throws JSONException {
Authentication userAuth = new UsernamePasswordAuthenticationToken(user, pass);
MyCellebriteAuthenticationProvider MCAP = new MyCellebriteAuthenticationProvider();
if (MCAP.authenticate(userAuth) == null){
response.put("isOk", false);
}
else{
SecurityContextHolder.getContext().setAuthentication(userAuth);
response.put("isOk", true);
response.put("token", "1234");
}
return response.toString();
}
}
However, this doesn't create a cookie.
Any idea or a better way to implement what I want to achieve?
Firstly, you should not do this manually:
SecurityContextHolder.getContext().setAuthentication(userAuth)
It is better to employ special filter responsible for authentication, setting security context and clearing it after request is handled. By default Spring Security uses thread locals to store security context so if you don't remove it after client invocation, another client can be automatically logged in as someone else. Remember that server threads are often reused for different request by different clients.
Secondly, I would recommend using basic or digest authentication for your RESTful web service. Both are supported by Spring Security. More in docs http://static.springsource.org/spring-security/site/docs/3.1.x/reference/basic.html
And finally, remember that RESTful web service should be stateless.
Also remember that Spring Security documentation is your friend. :-)