I got an app and I wanna create a connection to my rest-api.
Each user will get a "token" which will automatically be refreshed by google and co. In my requests, I will send the token and if it can be resolved to the user, the request should be answered, else if it is not up to date, I just wanna drop the request and return an error.
Are there still some possibilities?
Thanks for your help!
Current starting:
https://gist.github.com/PascalKu/97bca9506ad4f31c9e13f8fe8973d75b
You need to implement custom authentication in spring. I did the same thing but I had a db like:
fb_email_address | user_id | other_fields...
You must create these classes:
#Component
class TokenAuthenticationFilter extends OncePerRequestFilter {
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain) {
String theToken = request.getParameter('theToken');
TokenAuthentication tokenAuth = new TokenAuthentication(theToken)
SecurityContextHolder.getContext().setAuthentication(tokenAuth)
}
}
You need to add the authentication provider to spring's security system:
#Configuration
#EnableWebSecurity
class WebConfigHolder extends WebSecurityConfigurerAdapter implements WebMvcConfigurer {
#Autowired private TokenAuthenticationProvider tokenAuthenticationProvider
#Override
#Autowired
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(tokenAuthenticationProvider)
}
}
Implement authentication provider which actually checks to see if the token is valid.
#Component
class TokenAuthenticationProvider implements AuthenticationProvider {
//called by provider manager at some point during execution of security filters, I think
//it's the security api's job to call this
//the fbauthentication we create in our fbauthenticationfilter gets passed into this
#Override
#Transactional
Authentication authenticate(Authentication auth) {
TokenAuthentication tokenAuthentication = (TokenAuthentication) auth;
String theToken = auth.getThetoken();
boolean theTokenIsInDB = ///CHECK TO SEE IF TOKEN IS IN DB
if(theTokenIsInDB) {
TokenAuthentication t = new TokenAuthentication();
t.setAuthenticated(true);
return t;
} else {
throw new BadCredentialsException("Could not find user");
}
}
#Override
boolean supports(Class<?> authentication) {
boolean ret = TokenAuthentication.isAssignableFrom(authentication)
return TokenAuthentication.isAssignableFrom(authentication)
}
}
You need a simple Authentication Class that is just the object that's used to store the credentials while spring is waiting for the thread to get to the spring security filter; once it gets to that filter it passes authentication objects to the providers that support them. This allows you to have multiple authentication methods like FB, Google, custom tokens, etc... In my app I use FB tokens and in my provider, I check to see if the FB token corresponds to an authorized email address on my whitelist of email addresses. If it does, the user gets access to my app.
public class TokenAuthentication extends Authentication{
String token;
boolean isAuthenticated = false;
public TokenAuthentication(String theToken) { this.token = theToken;}
//getters and setters
}
What this code all does is, whenever someone accesses your API such as /api/person/get?theToken=132x8591dkkad8FjajamM9
The filter you created is run on every request. It checks to see if theToken was passed in and adds the TokenAuthentication to spring security.
At some point in the filter chain, spring security filter will run, and it will see that a TokenAuthentication has been created, and will search for a provider that can perform authentication on that. That happens to be your TokenAuthenticationProvider.
TokenAuthenticationProvider does the actual authentication. If it returns an authentication object that has isAuthenticated set to true, then the user will be allowed to access that api call.
Once authenticated, a user doesn't need to pass theToken again until his cookies are cleared or you invalidate his session. So he can call /api/person without the query parameters for the rest of his interactions. That's because the authentication is stored as a session-scoped data in spring.
Hope that helps. Let me know if anything's missing.
Related
I have integrated Spring Cloud Gateway with OAuth2 server. It works well with single instance gateway. here is my security config.
#EnableWebFluxSecurity
public class GatewaySecurityConfiguration {
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange().pathMatchers("/user/v3/api-docs", "/actuator/**").permitAll()
.anyExchange().authenticated()
.and()
.oauth2Login()
.and()
.csrf().disable();
return http.build();
}
But, When I scale gateway to 2 instances, some requests works expected, however some requests return 401.
load balancer (kubernetes nodeport service)
/ \
gateway gateway
\ /
(microservice clusters)
When I logged in first instance of gateway, the principal object is created successfully and also assign session to redis. If next request comes to second instance, it returns 401 because it has not principal.
how can i solve this problem?
ps: i am using redis for web sessions to commonize session informations between gateways.
TL;DR
You can share session principal information on Redis through WebSession. But you can't share access token(JWT), because they are stored in-memory at servers.
Solution-1: Your requests should always go to the server where you are logged in. (details below)
Solution-2: Implement new ReactiveOAuth2AuthorizedClientService bean that stores sessions in redis. (details below too)
Long Answer
From Spring Cloud documentation (https://cloud.spring.io/spring-cloud-static/Greenwich.SR5/multi/multi__more_detail.html);
The default implementation of ReactiveOAuth2AuthorizedClientService
used by TokenRelayGatewayFilterFactory uses an in-memory data store.
You will need to provide your own implementation
ReactiveOAuth2AuthorizedClientService if you need a more robust
solution.
The first thing you know: When you login successfully, the access token(as jwt) is returned by oauth2 server, and server creates session and this session is mapped to access token on ConcurrentHashMap (authorizedClients instance InMemoryReactiveOAuth2AuthorizedClientService class).
When you request API Gateway to access microservices with your session id, the access token(jwt) is resolved by TokenRelayGatewayFilterFactory in gateway, and this access token is set in Authorization header, and the request is forwarding to microservices.
So, let me explain how TokenRelayGatewayFilterFactory works (assume that you use WebSession through Redis and you have 2 gateway instances and you logged in at instance-1.)
If your request goes to instance-1, the principal is get back by session id from redis, then authorizedClientRepository.loadAuthorizedClient(..) is called in filter. This repository is instance of AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository object. The isPrincipalAuthenticated() method returns true, so the flow goes on authorizedClientService.loadAuthorizedClient(). this service is defined as ReactiveOAuth2AuthorizedClientService interface, and it has only one implementation ( InMemoryReactiveOAuth2AuthorizedClientService). This implementation has ConcurrentHashMap(key: principal object, value: JWT)
If your request goes to instance-2, all flow above are valid. But reminder that ConcurrentHashMap no access token to principal, because the access token is stored in instance-1's ConcurrentHashMap. So, the access token is empty, then your request downstreams without Authorization header. You will get 401 Unauthorized.
Solution-1
So, your requests should always go to the server where you are logged in to get valid access token.
If you use NGINX as load balancer, then use ip_hash in upstream.
If you use kubernetes service as load balancer, then use ClientIP in session affinity.
Solution-2
InMemoryReactiveOAuth2AuthorizedClientService is only implementation of ReactiveOAuth2AuthorizedClientService. So, create new implementation that uses Redis, and then do it primary bean.
#RequiredArgsConstructor
#Slf4j
#Component
#Primary
public class AccessTokenRedisConfiguration implements ReactiveOAuth2AuthorizedClientService {
private final SessionService sessionService;
#Override
#SuppressWarnings("unchecked")
public <T extends OAuth2AuthorizedClient> Mono<T> loadAuthorizedClient(String clientRegistrationId, String principalName) {
log.info("loadAuthorizedClient for user {}", principalName);
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
Assert.hasText(principalName, "principalName cannot be empty");
// TODO: When changed immutability of OAuth2AuthorizedClient, return directly object without map.
return (Mono<T>) sessionService.getSessionRecord(principalName, "accessToken").cast(String.class)
.map(mapper -> {
return new OAuth2AuthorizedClient(clientRegistration(), principalName, accessToken(mapper));
});
}
#Override
public Mono<Void> saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal) {
log.info("saveAuthorizedClient for user {}", principal.getName());
Assert.notNull(authorizedClient, "authorizedClient cannot be null");
Assert.notNull(principal, "principal cannot be null");
return Mono.fromRunnable(() -> {
// TODO: When changed immutability of OAuth2AuthorizedClient , persist OAuthorizedClient instead of access token.
sessionService.addSessionRecord(principal.getName(), "accessToken", authorizedClient.getAccessToken().getTokenValue());
});
}
#Override
public Mono<Void> removeAuthorizedClient(String clientRegistrationId, String principalName) {
log.info("removeAuthorizedClient for user {}", principalName);
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
Assert.hasText(principalName, "principalName cannot be empty");
return null;
}
private static ClientRegistration clientRegistration() {
return ClientRegistration.withRegistrationId("login-client")
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.clientId("dummy").registrationId("dummy")
.redirectUriTemplate("dummy")
.authorizationUri("dummy").tokenUri("dummy")
.build();
}
private static OAuth2AccessToken accessToken(String value) {
return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, value, null, null);
}
}
Notes:
SessionService is my custom class that interacts redis with reactivehashoperations instance.
The best way is to store OAuth2AuthorizedClient instead of access token. But, it is too hard now (https://github.com/spring-projects/spring-security/issues/8905)
The TokenRelayGatewayFilterFactory uses an in-memory data store to store the OAuth2AuthorizedClient which includes the (JWT) access token. This data store is not shared between multiple gateways.
To share the OAuth2AuthorizedClient information with Spring Session through Redis provide the following configuration:
#Bean
public OAuth2AuthorizedClientRepository authorizedClientRepository() {
return new HttpSessionOAuth2AuthorizedClientRepository();
}
For reactive WebSessions:
#Bean
public ServerOAuth2AuthorizedClientRepository authorizedClientRepository() {
return new WebSessionServerOAuth2AuthorizedClientRepository();
}
Further information for this configuration can be found at https://github.com/spring-projects/spring-security/issues/7889
I want to make API(s) using ASP.NET WEB API which should be private or protected.
Using the API(s) I am planning to make Xamarin application and a MVC Website.
Only the Apps can use the API(s), otherwise if anyone get the API(s) then he/she can retrieve data using the API(s). I don't want so!
How can I do it? I need some suggestion.
You can secure you api with API Key Authentication mechanism. Here is a good tutorial
Starting go inside your global.asax.cs file and add
GlobalConfiguration.Configuration.MessageHandlers.Add(new AuthHandler())
Create a class AuthHandler in your project and make that class interface with DelegatingHandler:
public class AuthHandler: DelegatingHandler
Create two methods within your AuthHandler class called ValidateCredentials and SendAsync. The SendAsync method is overridded.
private bool ValidateCredentials(AuthenticationHeaderValue authVal){}
protected override async Task<HttpResponseMessage> SendAsync(HttpResponseMessage request, CancellationToken cancelTok){}
When a class or method has the Authorize filter applied, the MessageHandler in your global.asax is called which calls the Auth handler you created, for example:
[Authorize]
public class SomeController : ApiControler{}
So whats left is the actual authentication of the user. You need to get the header value (placed by the client application), decode it and check it against your database or whatever you use.
private bool ValidateCredentials(AuthenticationHeaderValue authVal)
{
try{
string decodedHeader = new Classes.Strings().decode(authVal);
this.user = // some query to check against database goes here
return true;
}
catch{
// some type of error control here
return false
}
}
protected override async Task<HttpResponseMessage> SendAsync(HttpResponseMessage request, CancellationToken cancelTok)
{
if(ValidateCredentials(request.Headers.Authorization))
{
// store user here to use around the api on this request
}
}
So in short HTTP needs to store your authentication header value. Use that value on each request to filter any class or function you require authentication on. Next, I would read up on http headers, specifically the Authentication header value.
For example, in an web appliation, I have a user model:
class User{
String username;
String email;
String passowrd;
boolean active;
Set<Role> roles;
}
The following operations are supported:
1 guest can register(create a new user)
2 user can upate its info
3 user with role of admin can set the `active` and `roles`
At the server side we use the SpringMVC to get the model User directly:
#RequestMapping(value = "", method = RequestMethod.POST)
protected Result create(#Valid #RequestBody User user, BindingResult bindingResult) {
.....
}
So far so good with normal workflow, but think about someone(not admin user) send that:
/user HTTP/Update
{
"username":"jk",
"active":true,
"roles":[{
id:"role_admin_id"
}]
}
If this requset is accepted, the user jk will have the role of super_admin, which is not expected.
How do you protect that?
First of all, #RequestBody User user you sent is just a regular object you wanna update. It's not Spring Security User. If you want to define User as in spring security user, you' ll have to implements UserDetails. Do you already have spring security setup correctly? I don't know if you use xml or java configuration. If you use java configuration, you can control the access by roles as follow:
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/resources/**", "/signup", "/about").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
.anyRequest().authenticated()
.and()
// ...
.formLogin();
}
Reference: http://docs.spring.io/spring-security/site/docs/current/reference/html/jc.html#authorize-requests
I'm trying to develop a custom JAAS login module, which consumes a token and get's the user data from different sources.
It should work as an authentication realm for wildfly 8.2.1 final. I've got a test servlet in which I want to authenticate so the allowed roles for a injected EJB is checked.
The test from this site: http://www.radcortez.com/custom-principal-and-loginmodule-for-wildfly/ In fact I even started with his git project.
Because I only use a token, I can not use request.login(username, password) to initiate the wildfly login process. So I tried this:
#Inject
private SampleEJB sampleEJB;
...
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String token = request.getParameter("token");
try{
context = new LoginContext("TokenLoginDomain", new TokenCallbackHandler(token));
context.login();
}catch(Exception e){
e.printStackTrace();
}
response.getWriter().println("username=" + exampleEJB.getPrincipalName());
}
My EJB looks like this:
#Stateless
public class SampleEJB {
#Resource
private EJBContext ejbContext;
#RolesAllowed("partner")
public String getPrincipalName() {
return ejbContext.getCallerPrincipal().getName();
}
}
How do I start the login process without credentials in a servlet, so the user is logged in as wildfly user?
You can't authenticate that way in Java EE. With the given code you'll only be authenticated within the LoginContext instance.
As you already found out request.login(username, password) triggers the authentication process, but it's limited to the username/password credential.
In this case you should not only write a custom LoginModule (identity store), but a custom authentication mechanism as well. Authentication mechanisms are the controllers so to speak in the authentication process, while the identity store is the model.
Custom authentication mechanisms can be added via the JASPIC SPI, which WildFly supports well.
You can register a ServerAuthModule (SAM) from the app with some utility code that you can copy from here: https://github.com/arjantijms/glassfish-sam-ee-namespaces/tree/master/src/main/java/javax/security/authenticationmechanism
Then actually register the SAM with code as shown here:
https://github.com/arjantijms/glassfish-sam-ee-namespaces/blob/master/src/main/java/test/SamAutoRegistrationListener.java
I'm using the SignalR Javascript client and ASP.NET ServiceHost. I need the SignalR hubs and callbacks to only be accessible to logged in users. I also need to be able to get the identity of the currently logged in user from the Hub using the FormsIdentity from HttpContext.Current.User.
How do I secure the hub's so that only authenticated users can use SignalR?
How do I get the identity of the currently logged in user from the Hub?
You should use the this.Context.User.Identity that is available from the Hub. See a related question
EDIT: To stop unauthenticated users:
public void ThisMethodRequiresAuthentication()
{
if(!this.Context.User.Identity.IsAuthenticated)
{
// possible send a message back to the client (and show the result to the user)
this.Clients.SendUnauthenticatedMessage("You don't have the correct permissions for this action.");
return;
}
// user is authenticated continue
}
EDIT #2:
This might be better, just return a message
public string ThisMethodRequiresAuthentication()
{
if(!this.Context.User.Identity.IsAuthenticated)
{
// possible send a message back to the client (and show the result to the user)
return "You don't have the correct permissions for this action.");
// EDIT: or throw the 403 exception (like in the answer from Jared Kells (+1 from me for his answer), which I actually like better than the string)
throw new HttpException(403, "Forbidden");
}
// user is authenticated continue
return "success";
}
You can lock down the SignalR URL's using the PostAuthenticateRequest event on your HttpApplication. Add the following to your Global.asax.cs
This will block requests that don't use "https" or aren't authenticated.
public override void Init()
{
PostAuthenticateRequest += OnPostAuthenticateRequest;
}
private void OnPostAuthenticateRequest(object sender, EventArgs eventArgs)
{
if (Context.Request.Path.StartsWith("/signalr", StringComparison.OrdinalIgnoreCase))
{
if(Context.Request.Url.Scheme != "https")
{
throw new HttpException(403, "Forbidden");
}
if (!Context.User.Identity.IsAuthenticated)
{
throw new HttpException(403, "Forbidden");
}
}
}
Inside your hub you can access the current user through the Context object.
Context.User.Identity.Name
For part 1. of your question you could use annotations like below (This worked with SignalR 1.1):
[Authorize]
public class MyHub : Hub
{
public void MarkFilled(int id)
{
Clients.All.Filled(id);
}
public void MarkUnFilled(int id)
{
Clients.All.UnFilled(id);
}
}
Something missing from the other answers is the ability to use SignalR's built in custom auth classes. The actual SignalR documentation on the topic is terrible, but I left a comment at the bottom of the page detailing how to actually do it (Authentication and Authorization for SignalR Hubs).
Basically you override the Provided SignalR AuthorizeAttribute class
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class CustomAuthAttribute : AuthorizeAttribute
Then you decorate your hubs with [CustomAuth] above the class declaration. You can then override the following methods to handle auth:
bool AuthorizeHubConnection(HubDescriptor hubDesc, IRequest request);
bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubContext, bool appliesToMethod);
Since I'm on IIS servers and have a custom auth scheme, I simply return true from the AuthorizeHubConnection method, because in my Auth HttpModule I already authenicate the /signalr/connect and /signalr/reconnect calls and save user data in an HttpContext item. So the module handles authenticating on the initial SignalR connection call (a standard HTTP call that initiates the web socket connection).
To authorize calls on specific hub methods I check method names against permissions saved in the HttpContext (it is the same HttpContext saved from the initial connect request) and return true or false based on whether the user has permission to call a certain method.
In your case you might be able to actually use the AuthorizeHubConnection method and decorate your hub methods with specific roles, because it looks like you are using a standardized identity system, but if something isn't working right you can always revert to brute force with HttpModule (or OWIN) middle-ware and looking up context data in on subsequent websocket calls with AuthorizeHubMethodInvocation.