Integrate Spring Boot Security SAML with Azure AD Gallery app as multi tenant - spring-boot

I am Developing Java Spring Boot System and trying to Integrate with Azure non-gallery app using SAML Single Sign-On.
I found how to create Non-gallery applications, how to apply non-gallery app to Azure Gallery list etc. For example this link is about configuring SAML SSO:
Configure SAML-based single sign-on
So I understood Azure side configurations and procedures.
I am using Spring Security SAML Extensions. But I can't find the Spring Boot side configuration even I made a lot of research except official SAML Extension documentation which is XML based.
By the way, my main goal is adding our organization app to Azure gallery app list. Our app used by a multiple companies so if we add our organization app to Azure Gallery App list our customers can configure their Azure AD account as SSO integration.
My question is below:
How to integrate Azure Non-Gallery App to Spring Boot app?
How to handle multiple Azure AD tenants?
Is anybody help me with this?
EDIT:
Currently I made a single tenant SSO login with Spring Boot and Azure AD non-gallery app. I configured IdP metadata using Azure AD Federation XML Metadata URL. You can see source code below:
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Value("${security.saml2.metadata-url}")
private String IdPMetadataURL;
#Value("${server.ssl.key-alias}")
private String keyStoreAlias;
#Value("${server.ssl.key-store-password}")
private String keyStorePassword;
#Value("${server.port}")
String port;
#Value("${server.ssl.key-store}")
private String keyStoreFile;
#Autowired
private SAMLUserService samlUserService;
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/saml/**", "/", "/login", "/home", "/about").permitAll()
.anyRequest().authenticated()
.and()
.apply(saml())
.webSSOProfileConsumer(getWebSSOProfileConsumerImpl())
.userDetailsService(samlUserService)
.serviceProvider()
.keyStore()
.storeFilePath(this.keyStoreFile)
.password(this.keyStorePassword)
.keyname(this.keyStoreAlias)
.keyPassword(this.keyStorePassword)
.and()
.protocol("https")
.hostname(String.format("%s:%s", "localhost", this.port))
.basePath("/")
.and()
.identityProvider()
.metadataFilePath(IdPMetadataURL)
.and();
}
public WebSSOProfileConsumerImpl getWebSSOProfileConsumerImpl(){
WebSSOProfileConsumerImpl consumer = new WebSSOProfileConsumerImpl();
consumer.setMaxAuthenticationAge(26000000); //300 days
return consumer;
}
}
From now I need to generate IdP Metadata XML instead of using IdP metadata URL. Using fields such as:
IdP Entity ID
IdP SSO URL
IdP Public certificate
The process is I am thinking about is:
Our customers register their Azure AD IdP fields above
My Spring Boot system automatically generate IdP Metadata XML
Then customer's Azure AD SSO can integrated to our system
If is there anything wrong please teach me out.

I'm using Spring Security SAML extension with Spring Boot. It's irrelevant which SAML IdP you are using as you only need the IdP meta data. You generate your SP meta data and use it as mentioned in the MS docs. You may check Spring Security SAML docs.

In order to list your application to Azure Gallery application list kindly go through the document. Kindly go through the entire procedure as mentioned in the document in order to list your application in azure Gallery.
Only for application which is already exists in the gallery the configuration side for the application is mentioned. For non-gallery application you need to configure the azure AD metadata value in the application end.

finally I did my solution for dynamic IDP. I used spring-boot-security-saml this simplified project.Thank you for ulisesbocchio this guy who implemented it.
Also big thanks to ledjon who shared me with his experience.
Here is how I'm configuring the saml part of the http security
http.apply(saml)
.serviceProvider()
.metadataGenerator()
.entityId(LocalSamlConfig.LOCAL_SAML_ENTITY_ID)
.entityBaseURL(entityBaseUrl)
.includeDiscoveryExtension(false)
.and()
.sso()
.successHandler(new SendToSuccessUrlPostAuthSuccessHandler(canvasAuthService))
.and()
.metadataManager(new LocalMetadataManagerAdapter(samlAuthProviderService))
.extendedMetadata()
.idpDiscoveryEnabled(false)
.and()
.keyManager()
.privateKeyDERLocation("classpath:/saml/localhost.key.der")
.publicKeyPEMLocation("classpath:/saml/localhost.cert")
.and()
.http()
.authorizeRequests()
.requestMatchers(saml.endpointsMatcher())
.permitAll();
The important part here is the
.metadataManager(new LocalMetadataManagerAdapter(samlAuthProviderService)) which is what we're trying to solve for here. The object samlAuthProviderService is a Bean-managed object and it contains the logic to actually retrieve the metadata from the database, so there's not a lot that is specially about it. But here is what my LocalMetadataManagerAdapter roughly looks like:
#Slf4j
public class LocalMetadataManagerAdapter extends CachingMetadataManager {
private final SamlAuthProviderService samlAuthProviderService;
public LocalMetadataManagerAdapter(SamlAuthProviderService samlAuthProviderService) throws MetadataProviderException {
super(null);
this.samlAuthProviderService = samlAuthProviderService;
}
#Override
public boolean isRefreshRequired() {
return false;
}
#Override
public EntityDescriptor getEntityDescriptor(String entityID) throws MetadataProviderException {
// we don't really want to use our default at all, so we're going to throw an error
// this string value is defined in the "classpath:/saml/idp-metadata.xml" file:
// which is then referenced in application.properties as saml.sso.idp.metadata-location=classpath:/saml/idp-metadata.xml
if("defaultidpmetadata".equals(entityID)) {
throw exNotFound("Unable to process requests for default idp. Please select idp with ?idp=x parameter.");
}
EntityDescriptor staticEntity = super.getEntityDescriptor(entityID);
if(staticEntity != null)
return staticEntity;
// we need to inject one, and try again:
injectProviderMetadata(entityID);
return super.getEntityDescriptor(entityID);
}
#SneakyThrows
private void injectProviderMetadata(String entityID) {
String xml =
samlAuthProviderService.getMetadataForConnection(entityID)
.orElseThrow(() -> exRuntime("Unable to find metadata for entity: " + entityID));
addMetadataProvider(new LocalMetadataProvider(entityID, xml));
// this will force a refresh/re-wrap of the new entity
super.refreshMetadata();
}
}
The important part here is the override of getEntityDescriptor() which will get called to get the metadata object at runtime. I'm also disabling refreshes by overriding isRefreshRequired() to return false. You can determine if this makes sense for your use case or not.
The referenced LocalMetadataProvider is just a wrapper class to store/return the xml string when required:
public class LocalMetadataProvider extends AbstractReloadingMetadataProvider {
private final String Id;
private final String xmlData;
public LocalMetadataProvider(String id, String xmlData) {
this.Id = id;
this.xmlData = xmlData;
setParserPool(LocalBeanUtil.getBeanOrThrow(ParserPool.class));
}
#Override
protected String getMetadataIdentifier() {
return this.Id;
}
#Override
protected byte[] fetchMetadata() throws MetadataProviderException {
return xmlData.getBytes();
}
}
Finally we can pass idp metadata entityID as a parameter. And retrieve entityID metadata from DB etc:
/saml/login?idp=X where X is the entityID value we want to get passed to getEntityDescriptor().

Related

Spring boot with keycloak add api key auth for specific endpoint

in spring boot app I have:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(jsr250Enabled = true)
#Slf4j
public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
#Autowired
List<String> aIPWhiteList;
#Autowired
List<String> bIPWhiteList;
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
String aIPAddressesFilterStr = defineIPFilters(aIPWhiteList);
String bIPAddressesPFilterStr = defineIPFiltersbIPWhiteList);
http.authorizeRequests()
.antMatchers("/order/a/**").access(aIPAddressesFilterStr)
.antMatchers("/b/order").access(bIPAddressesFilterStr)
.anyRequest().permitAll();
http.cors().and().csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
private String defineP4IPFilters(List<String> whiteList) {
StringBuilder ipAddressesFilterStr = new StringBuilder();
for (String ip: whiteList) {
ipAddressesFilterStr.append("hasIpAddress('").append(ip).append("') or ");
}
return ipAddressesFilterStr.substring(0, ipAddressesFilterStr.length() - 4);
}
}
I wonder how can I for this "b/order" make another auth, based on API Key stored in headers.
Basically only for this 1 endpoint I want authorize users differently.
Other endpoints are authorized by keycloak, are done from registered users.
But here I would like to auth it only by api key that is static.
any ideas ?
thanks!
I have two ideas which should save you quite some trouble, even if does not answer directly your question:
Do not use KeycloakWebSecurityConfigurerAdapter
It is part of the (very) deprecated Keycloak adapters for spring. Use spring-boot-starter-oauth2-resource-server instead. Refer to those tutorials for various ways to do it (with Keycloak)
Use OAuth2 client-credentials flow in place of API key
It serves that exact purpose: authenticate a trusted programmatic clients with "static" secrets.
With Keycloak, just declare "confidential" clients ("Client authentication" set to "On" and "Service Accounts Roles" enabled). Secret is to be retrieved from a "credentials" tab for this clients in Keycloak admin console. You can then define and assign different roles for each client if needed (such roles will appear in access-tokens, so you'll be able to use it in spring-security access control decisions)
Such clients will authorize their requests to resource-server(s) with access-tokens issued by your Keycloak instance just as other clients (used by humans) do. Only the protocol to get tokens (OAuth2 flow) differs: client-credentials for "robots" and authorization-code for "humans".
From the resource-server point of view, there will be absolutely no difference: all requests will be authorized with access-tokens issued by the same authorization-server => no need for a different authentication mechanism on some endpoints, just apply regular role-based access-control or wahtever else written with spring-security expressions.

Can I use both introspection server and local check for authorize token? Spring Boot - Security

I want to
introspect JWT token on remote server
and then check locally if scope/aud/iss/exp are correct
How can this be done most easily in Spring Boot?
As I understand first case is something similar to opauqeToken functionality (but I have normal JWT) and second case is more like using jwt
Spring Security only supports JWTs or Opaque Tokens, not both at the same time.
If I use opaqueToken, then validation on remote server is done without any effort (even if that's JWT)
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.mvcMatchers("/api/**").hasAuthority("SCOPE_" + scope)
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.opaqueToken(opaque -> opaque
.introspectionUri(this.introspectionUri)
.introspectionClientCredentials(this.clientId, this.clientSecret)
));
return http.build();
I have scope verified. Now I want to check iss, aud, exp. Is that doable with opaqueToken?
Or should I use jwt auth instead?
IMHO opaqueToken can be JWT, so now the question is how to verify and inspect it locally after remote introspection?
It's kind of hybrid of two different approaches, but hopefully you know the simple way how to do it.
Ok, I think I have my answer. I created my own introspector which is implementing OpaqueTokenIntrospector
public class JwtOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
private OpaqueTokenIntrospector delegate =
new NimbusOpaqueTokenIntrospector(
"introspect-url",
"client-id",
"client-secret"
);
#Override
public OAuth2AuthenticatedPrincipal introspect(String token) {
OAuth2AuthenticatedPrincipal introspected = this.delegate.introspect(token);
// SOME LOGIC
}
}
and I added it as a #Bean
#Bean
public OpaqueTokenIntrospector tokenIntrospector() {
return new JwtOpaqueTokenIntrospector();
}

Adding support for multi-tenancy in Spring Boot application using Spring Security

I am new to Spring Security and Oauth2. In my Spring Boot application, I have implemented authentication with OAuth2 for one tenant. Now I am trying to multi-tenancy in my Spring Boot application. From the answer to the previous post: OAUTH2 user service with Custom Authentication Providers, I have implemented two security configurations in order to support two tenants: Tenant1 and Tenant2 as follows:
Custom OAuth2 user service is as follows:
#Component
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
private UserRepository userRepository;
#Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
...
}
}
Tenant 1 security configuration is as follows:
#Configuration
public class Tenant1SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final CustomOAuth2UserService customOAuth2UserService;
public SecurityConfiguration(CustomOAuth2UserService customOAuth2UserService) {
this.customOAuth2UserService = customOAuth2UserService;
}
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/login**").permitAll()
.antMatchers("/manage/**").permitAll()
.antMatchers("/api/auth-info").permitAll()
.antMatchers("/api/**").authenticated()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/info").permitAll()
.antMatchers("/management/prometheus").permitAll()
.antMatchers("/management/**").hasAuthority("ADMIN")
.antMatchers("/tenant1/**").authenticated()
.and()
.oauth2Login()
.userInfoEndpoint().userService(oauth2UserService());
http
.cors().disable();
}
private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
return customOAuth2UserService;
}
}
Tenant 2 security configuration is as follows:
#Order(90)
#Configuration
public class Tenant2SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(new AntPathRequestMatcher("/tenant2/**"))
.csrf().disable()
.authorizeRequests()
.antMatchers("/tenant2/**").hasAuthority("USER")
.and()
.httpBasic();
http
.cors().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user")
.password("password")
.roles("USER");
}
}
application properties are as given below:
clientApp.name=myapp
spring.security.oauth2.client.registration.keycloak.client-id=abcd
spring.security.oauth2.client.registration.keycloak.client-name=Auth Server
spring.security.oauth2.client.registration.keycloak.scope=api
spring.security.oauth2.client.registration.keycloak.provider=keycloak
spring.security.oauth2.client.registration.keycloak.client-authentication-method=basic
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
myapp.oauth2.path=https://my.app.com/oauth2/
spring.security.oauth2.client.provider.keycloak.token-uri=${myapp.oauth2.path}token
spring.security.oauth2.client.provider.keycloak.authorization-uri=${myapp.oauth2.path}authorize
spring.security.oauth2.client.provider.keycloak.user-info-uri=${myapp.oauth2.path}userinfo
spring.security.oauth2.client.provider.keycloak.user-name-attribute=name
Basically, the intent of my application is B2B. So if I want to onboard a new business entity B as a tenant of my application, plugin its authentication provider, all its existing users should get authenticated seamlessly.
So, in view of the above, I have thought of the approach (though I am not sure if it's the best approach) as follows:
There can be a single endpoint for all the tenants i.e. there can be a common login page for all the users regardless of the tenant. On this login page, there can be the provision for the users to enter only email IDs.
The tenant ID can be determined from the email ID entered by the user.
Based on tenant ID, authentication provider of associated tenant ID gets invoked in order to authenticate the user of associated tenant.
On successful authentication, redirect to the home page for the associated tenant as: https://my.app.com/<tenant-id>/
In addition to the above, I would like to build a setup, where my application has quite a few, say, 40 tenants, out of which say 20 tenants use OAuth2, 10 uses basic auth and 10 uses form login.
Here in order to implement the above type of functionality, from Multi tenancy for spring security, it seems I have to support one authentication method, add tenant ID to authentication token and then create an adapter to other authentication methods, as needed.
But, in this regard, I did not find any concrete idea in any post so far on what changes should I do in the existing code base in order to achieve this.
Could anyone please help here?

Spring OAuth2 Client Credentials with UI

I'm in the process of breaking apart a monolith into microservices. The new microservices are being written with Spring using Spring Security and OAuth2. The monolith uses its own custom security that is not spring security, and for now the users will still be logging into the monolith using this homegrown security. The idea is that the new MS apps will have their own user base, and the monolith app itself will be a "user" of these Services. I've successfully set up an OAuth2 Auth Server to get this working and I'm able to log in with Client Credentials to access the REST APIs.
The problem is that the Microservices also include their own UIs which will need to be accessed both directly by admins (using the new Microservice users and a login page) and through the monolith (hopefully using client credentials so that the monolith users do not have to log in a second time). I have the first of these working, I can access the new UIs, I hit the login page on the OAuth server, and then I'm redirected back to the new UIs and authenticated & authorized.
My expectation from the is that I can log in to the OAuth server with the client credentials behind the scenes and then use the auth token to have the front end users already authenticated on the front end.
My question is - what should I be looking at to implement to get the client credentials login to bypass the login page when coming in through the UI? Using Postman, I've gone to http://myauthapp/oauth/token with the credentials and gotten an access token. Then, I thought I could perhaps just GET the protected UI url (http://mymicroservice/ui) with the header "Authorization: Bearer " and I was still redirected to the login page.
On the UI app:
#Configuration
#EnableOAuth2Client
protected static class ResourceConfiguration {
#Bean
public OAuth2ProtectedResourceDetails secure() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setId("secure/ui");
details.setClientId("acme");
details.setClientSecret("acmesecret");
details.setAccessTokenUri("http://myoauthserver/secure/oauth/token");
details.setUserAuthorizationUri("http://myoauthserver/secure/oauth/authorize");
details.setScope(Arrays.asList("read", "write"));
details.setAuthenticationScheme(AuthenticationScheme.query);
details.setClientAuthenticationScheme(AuthenticationScheme.form);
return details;
}
#Bean
public OAuth2RestTemplate secureRestTemplate(OAuth2ClientContext clientContext) {
OAuth2RestTemplate template = new OAuth2RestTemplate(secure(), clientContext);
AccessTokenProvider accessTokenProvider = new AccessTokenProviderChain(
Arrays.<AccessTokenProvider> asList(
new AuthorizationCodeAccessTokenProvider(),
new ResourceOwnerPasswordAccessTokenProvider(),
new ClientCredentialsAccessTokenProvider())
);
template.setAccessTokenProvider(accessTokenProvider);
return template;
}
}
SecurityConfig:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private OAuth2ClientContextFilter oAuth2ClientContextFilter;
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.anonymous().disable()
.csrf().disable()
.authorizeRequests()
.antMatchers("/ui").hasRole("USER")
.and()
.httpBasic()
.authenticationEntryPoint(oauth2AuthenticationEntryPoint());
}
private LoginUrlAuthenticationEntryPoint oauth2AuthenticationEntryPoint() {
return new LoginUrlAuthenticationEntryPoint("/login");
}
}

Authentication and authorization in REST Services with Liferay

We are building some services that will be exposed through a RESTful API. Primary customers of this API are Liferay portlets using Angular JS, meaning there are direct calls from client-side (Angular) to our services.
So far we have designed an authentication and authorization mechanism to assure that we can identify which logged user (Liferay) is requesting our API.
PS.: note that although we are using Liferay, it could be any other Java based application instead.
What we have designed is:
When the user logs in in our portal, Liferay creates an authentication token with userLogin (or ID) + client IP + timestamp. This token is saved in a cookie;
Before every REST call, Angular reads this cookie and sends its contents via a HTTP header;
Our service "decrypts" the cookie content sent and verifies if the timestamp is valid, the IP is the same and, according to our business rules, if the user has access to do or read whatever he wants to.
This design looks consistent to us right now and, depending on the algorithm we choose to create this token, we believe it is a secure approach.
Our doubts are:
Are we, somehow, reinventing the wheel not using HTTP authentication with some kind of custom provider? How to?
Could Spring Security help us with that? We have read some articles about it but it's not clear if it makes sense to use it with a non-Spring application;
Are there any security flaws we have not considered with this approach?
Thank you in advance. Any help is appreciated.
Filipe
Spring security solves the problem description, and as a bonus you will get all the spring security features for free.
The Token approach is great and here is how you can secure your APIs with spring-security
Implements AuthenticationEntryPoint and have the commence method set 401 instead of re-direction 3XX as follows
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,"Access Denied");
Have a TokenProcessingFilter extend and leverage what UsernamePasswordAuthenticationFilter has to offer, override the doFilter() method, extract the the token from the request headers, validate and Authenticate the token as follows
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = this.getAsHttpRequest(request);
String authToken = this.extractAuthTokenFromRequest(httpRequest);
String userName = TokenUtils.getUserNameFromToken(authToken);
if (userName != null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(userName);
if (TokenUtils.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication =new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
Your Spring-security configuration will look like
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthFailure authFailure;
#Autowired
private AuthSuccess authSuccess;
#Autowired
private EntryPointUnauthorizedHandler unauthorizedHandler;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private AuthenticationTokenProcessingFilter authTokenProcessingFilter;
#Autowired
public void configureAuthBuilder(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // Restful hence stateless
.and()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler) // Notice the entry point
.and()
.addFilter(authTokenProcessingFilter) // Notice the filter
.authorizeRequests()
.antMatchers("/resources/**", "/api/authenticate").permitAll()
//.antMatchers("/admin/**").hasRole("ADMIN")
//.antMatchers("/providers/**").hasRole("ADMIN")
.antMatchers("/persons").authenticated();
}
}
-- Last you will need another end point for Authentication and token-generation
Here is a spring MVC example
#Controller
#RequestMapping(value="/api")
public class TokenGenerator{
#Autowired
#Lazy
private AuthenticationManager authenticationManager;
#Autowired
private UtilityBean utilityBean;
#Autowired
private UserDetailsService userDetailsService;
#RequestMapping(value="/authenticate", method=RequestMethod.POST, consumes=MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<?> generateToken(#RequestBody EmefanaUser user){
ResponseEntity<?> response = null;
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserId(),user.getCredential());
try {
Authentication authentication = authenticationManager.authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
/*
* Reload user as password of authentication principal will be null
* after authorization and password is needed for token generation
*/
UserDetails userDetails = userDetailsService.loadUserByUsername(user.getUserId());
String token = TokenUtils.createToken(userDetails);
response = ResponseEntity.ok(new TokenResource(utilityBean.encodePropertyValue(token)));
} catch (AuthenticationException e) {
response = ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
return response;
}
}
1 Generate token, 2. subsequent API-calls should have the token
Yes spring-security can do this and you don`t have to break new grounds in Authentication, Authorization.
Hope this helps
I'm late to the party but here are my two cents.
Disclaimer: The previous answers are a possible way to tackle this.
The next insight is what I've learned while implementing RESTful APIs
in Liferay.
If I understand correctly the question then you have two scenarios here. The first one is you need to create a RESTful api that will be called by already Logged in users. This means that the AJAX calls will, probably, get execute within the client's renderization of the portal. The main issue here is the security, how to secure yous REST calls.
First of all I think one should try to leverage on whatever framework one is using before implementing something else. Liferay DOES uses Spring in the backend but they've already implemented security. I would recommend to use the Delegate Servlet.
This servlet will execute any custom class and put it inside Liferay's Authentication path, meaning that you could just use PortalUtil.getUser(request) and if it's 0 or null then the user is not authenticated.
In order to use the delegate servlet you just need to configure it in your web.xml file
<servlet>
<servlet-name>My Servlet</servlet-name>
<servlet-class>com.liferay.portal.kernel.servlet.PortalDelegateServlet</servlet-class>
<init-param>
<param-name>servlet-class</param-name>
<param-value>com.samples.MyClass</param-value>
</init-param>
<init-param>
<param-name>sub-context</param-name>
<param-value>api</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
As you can see we are instantiating another servlet. This servlet is going to be defined by the PortalDelegateServlet. The Delegate Servlet will use whatever class is on the value of the sevlet-class param. Within that class you can just check if there's a valid username in the HttpServletRequest object with Liferay's Utils and if there is then the user is OK to go.
Now, the way you access this is that the Delegate Servlet uses the value of the sub-context to know which class are you refering to from the URL. So, in this example you'll be access com.samples.MyClass by going to https://my.portal/delegate/api The 'delegate' part will always be there, the second part of the URL is what we define in the init-param. Notice that you can only define one level of the URI for sub-context, i.e. you can't set /api/v2.0/ as sub-context.
From then on you can do whatever you want on your servlet class and handle the parsing of the REST URI as you want.
You can also use spring's Dispatcher class as the class that the Delegate Servlet will call and just setup a spring servlet, hence having url annotation mappins.
It is important to know that this is only good for RESTful or Resource
serving, since the Delegate Servlet will not know how to handle
renderization of views.
The second scenario you have is to be able to call this RESTful API from any external application (doesn't matter what implementation they have). This is an entire different beast and I would have to reference the answer by iamiddy and using Spring's Authentication Token could be a nice way to do this.
Another way to do this, would be to handle unauthorized users in your servlet class by sending them to the login page or something of the sort. Once they succesfully login Liferay's Utils should recognize the authenticated user with the request. If you want to do this within an external application then you would need to mock a form-based login and just use the same cookie jar for the entire time. Although I haven't tried this, in theory it should work. Then again, in theory, communism works.
Hope this help some other poor soul out there.
Take a look at Single Sign On and Spring Security OAuth2 token authentication.
Here is example: sso-with-oauth2-angular-js-and-spring-security.
Note that Spring 4.2 might have some handy CORS support.
I can't uprate someone's answer with my current rating but The answer above is probably the right direction.
It sounds like what you need to investigate is something named CORS which provides security with cross site scripting. I'm sorry I don't quite know how it works yet (I'm in the same situation) but this is the main topic of this NSA document on REST
For Spring, try here to start maybe?

Resources