I was trying it implement custom authentication, Authentication works fine, but have problems with Authorization. I am using JWT tokens, Any API I try to access it throwing me a 403 forbidden error. I am not sure what is wrong. I have the full source code in github. https://github.com/vivdso/SpringAuthentication, Spring boot magic is not working on this. Any pointers are apperciated.
Using MongoDb as my repository to store user accounts and roles.
InMemory Authentication is working fine, but Custom Authentication always returs 403, Below is my I extended WebSecurityConfigurerAdapter
#Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
// authenticationManagerBuilder.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");
// authenticationManagerBuilder.inMemoryAuthentication().withUser("user").password("user").roles("USER");
authenticationManagerBuilder.authenticationProvider(getCustomAuthenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/customer").hasAuthority("ADMIN")
.antMatchers(HttpMethod.GET, "/order").hasAuthority("USER").and()
.csrf().disable();
}
#Bean
protected CustomAuthenticationProvider getCustomAuthenticationProvider(){
return new CustomAuthenticationProvider();
}
I don't have any custom implementation for authorization.
The issue is resolved, I have updated Github repository. The spring boot security was working fine, the issue was the roles assigned to the user collection was a Json string object (e.g. {"role":"ROLE_ADMIN"}) instead of sting object "ROLE_ADMIN".
Thanks
Related
I am trying to use keycloak for authorization in spring cloud gateway. Keycloak does not provide any spring based adapters for policy enforcement for reactive stack.However, it does provide an endpoint for policy evaluation.
http://localhost:8080/realms/myrealm/protocol/openid-connect/token -- POST
Request:
grant_type:urn:ietf:params:oauth:grant-type:uma-ticket
response_mode:decision
audience:b2b
permission:spm_audit#GET
Header:
Authorization : bearer <JWT>
# spm_audit is the resource that I have created in keycloak and GET is the scope(using HTTP methods as api scopes).
RESPONSE:
{
"result": true
}
My problem is that above endpoint does not accept URI as permission in request body and I don't have any resource-name to request URL mapping at gateway.
One possible solution could be to use gateway's route id as resource name and pass it in permission
cloud:
gateway:
routes:
- id: spm_audit
uri: http://localhost:8001
predicates:
- Path=/gateway/spm/api/v1/registrations/{regUUID}/audit
filters:
- StripPrefix=1
metadata:
custom_scope: "test scope"
#Fetch the route info in auth manager
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); //(ServerWebExchange exchange)
route.getId();
The problem with this approch is that the route matching filters are applied after authorization filter and exchange.getAttribute(GATEWAY_ROUTE_ATTR) is coming as null, plus I will have to map all api paths in route configuration and will end up with a huge configuration file.
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, #Qualifier("keycloKWebClient")WebClient kycloakWebClient) {
http
.authorizeExchange()
.pathMatchers(
"/gateway/*/public/**")
.permitAll()
.and()
.authorizeExchange()
.anyExchange()
.access(keyalokAuthManager(kycloakWebClient))....#this is where I call policy evaluation api
https://www.keycloak.org/docs/latest/authorization_services/index.html#_service_authorization_api
What about using spring-security for resource-servers with a JWT decoder? It would be far more efficient as it would save many round trips to authorization-server (JWT decoder validates access-token with authorization-server public key downloaded once when policy enforcer requires a call to authorization-server for each and every incoming "non public" request).
You can map Keycloak "roles" to spring "granted authorities" and apply Role Based Access Control either with:
http.authorizeExchange().pathMatchers("/protected-route/foo").hasAuthority("ROLE_IN_KEYCLOAK") in your Java conf
#PreAuthorize("hasAnyAuthority('ROLE_IN_KEYCLOAK')") on your components methods.
For that, all you have to do is provide with an authentication converter with custom authorities converter:
interface AuthenticationConverter extends Converter<Jwt, Mono<JwtAuthenticationToken>> {}
interface AuthoritiesConverter extends Converter<Map<String, Object>, Collection<? extends GrantedAuthority>> {}
#Bean
AuthoritiesConverter authoritiesConverter() {
return claims -> {
final var realmAccess = (Map<String, Object>) jwt.getClaims().getOrDefault("realm_access", Map.of());
final var realmRoles = (Collection<String>) realmAccess.getOrDefault("roles", List.of());
// concat client roles to following stream if your app uses client roles in addition to realm ones
return realmRoles.stream().map(SimpleGrantedAuthority::new).toList();
}
}
#Bean
public AuthenticationConverter authenticationConverter(AuthoritiesConverter authoritiesConverter) {
return jwt -> Mono.just(new JwtAuthenticationToken(jwt, authoritiesConverter.convert(jwt.getClaims())));
}
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, AuthenticationConverter authenticationConverter) {
http.oauth2ResourceServer().jwt()
.jwtAuthenticationConverter(authenticationConverter);
}
You should also have a look at this repo: https://github.com/ch4mpy/spring-addons
There is a spring-addons-webflux-jwt-resource-server spring-boot (2.7 or later) starter which would save you quite some configuration hassle. Tutorials in this repo are using servlet variants of the libs, but the 4 starters (servlet/reactive with JWT-decoder/introspection) work the same and you should easily find what to adapt for your reactive app.
I have configured Liferay v7.3.4 CE to authenticate with AWS Cognito using OpenID Connect Provider, and that all works fine.
I would now like to invoke REST APIs in AWS, from within Liferay, using the JWT token obtained from Cognito during the sign-in process.
It would seem this JWT token should be available within Liferay, correct? If so, a source code example demonstrating how to access this would be very much appreciated.
This token would then be added to the Authorization header of API calls to an instance of the AWS API Gateway secured by the same Cognito instance from which the user has just signed in. But first things first... how would someone programmatically access the JWT token for the current Liferay session?
Hope this makes sense.
I've got this working.
First, I am using Maven (not gradle) to build Liferay projects. To this end, I've added the following to my portlet's pom.xml file:
<dependency>
<groupId>com.liferay</groupId>
<artifactId>com.liferay.portal.security.sso.openid.connect.api</artifactId>
<scope>provided</scope>
</dependency>
Next, in my portlet's render method, I've added the following code:
public void render(RenderRequest renderRequest, RenderResponse renderResponse) throws PortletException, IOException
{
try {
// get the jwtToken from the renderRequest parameter
String jwtToken = null;
HttpSession session = PortalUtil.getOriginalServletRequest(PortalUtil.getHttpServletRequest(renderRequest)).getSession();
if (session.getAttribute(OpenIdConnectWebKeys.OPEN_ID_CONNECT_SESSION) instanceof OpenIdConnectSession) {
OpenIdConnectSession openIdConnectSession = (OpenIdConnectSession) session.getAttribute(OpenIdConnectWebKeys.OPEN_ID_CONNECT_SESSION);
jwtToken = openIdConnectSession.getAccessTokenValue();
}
// call a REST API with the jwt token
List<Organization> organizations = masterDataClient.fetchOrganizations(jwtToken);
// do other stuff
super.render(renderRequest, renderResponse);
} catch (Exception e) {
throw new PortletException(e);
}
}
I can not access secured resource from another Origin. Searched a few days for solution and didn't find it, so I posted question here.
This is the story:
I created first Spring Boot Application that runs on default port 8080.
It depends on spring-boot-starter-data-rest and other dependencies and it has a GreetingRepository:
public interface GreetingRepository extends JpaRepository<Greeting, Long>, JpaSpecificationExecutor<Greeting> {}
globally enables CORS with RepositoryRestConfigurerAdapter:
#Configuration
public class GlobalRepositoryRestConfigurer extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.getCorsRegistry()
.addMapping("/**")
.allowedOrigins("*")
.allowedHeaders("*")
.allowedMethods("*");
}
}
I created second Spring Boot Application that runs on port 9000 that will access this Greetings resource.
And it works. Second application sends HTTP request with method GET to http://localhost:8080/api/greetings and it gets response with Json data, with HEADER Access-Control-Allow-Origin: *. Everything is fine.
But.
Then I wanted to secure my resource in first application. There I included spring-boot-starter-security dependency and made configuration in WebSecurityConfigurerAdapter:
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService myUserDetailsService;
#Autowired
PasswordEncoder myPasswordEncoder;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and()
.httpBasic()
.and()
.csrf().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService).passwordEncoder(myPasswordEncoder);
}
#Bean
public UserDetailsService createBeanUserDetailService() {
return new MyUserDetailsService();
}
#Bean
public PasswordEncoder createBeanPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
and made UserDetailsService and so on. (Important: I tested security before adding CORS, so this security configuration works and that is not a problem).
Then, after adding security in first application, second application sends same HTTP request with method GET to http://localhost:8080/api/greetings as the first time.
Now it gets an error:
Failed to load http://localhost:8080/api/greetings: Redirect from 'http://localhost:8080/api/greetings' to 'http://localhost:8080/login' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:9000' is therefore not allowed access.
I can not find solution for this problem. So CORS works for Spring Repository resources, and Spring Security works, but I can not access secured resource from another Origin because of /login page. How to solve this?
When Spring security is enabled, the security filters take precedence over CorsFilter. To make Spring Security aware of your CORS configuration call the cors() method in your HttpSecurity setup.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and()
.httpBasic()
.and()
.csrf().disable();
}
This will give CorsFilter precedence over other Spring security filters. CorsFilter recognizes CORS preflight requests (HTTP OPTIONS calls) and allows it to go through without any security.
I am building a REST based web application and i have applied the spring security to it successfully but my concern is that i have added a custom filter
http
.authorizeRequests()
.antMatchers("/mypro/userCont/signup").permitAll()
.and()
.addFilterBefore(new CustomFilter(), UsernamePasswordAuthenticationFilter.class)
.httpBasic();
i want that this filter should only run for secured urls not for unsecured urls if it get runs for non secured url then it should not interrupt me to get resource on non-secure url.
My scenerio is if user is not logged in for secured url custom filter should be run which check the Principal using below code:
Authentication auth = (Authentication) SecurityContextHolder.getContext().getAuthentication();
if the auth is null user should seen default spring security login popup
and if i hit the non secure url i should get to the resource.
Can anyone help me.
You can skip security entirely for particular resources by configuring WebSecurity. In this case, Spring security will totally ignore that URL pattern:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity webSecurity) throws Exception
{
webSecurity
.ignoring()
.antMatchers("/resources/**"); //skip security entirely
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.addFilter(mycustomFilter)
.authorizeRequests().anyRequest()
.and()
.httpBasic();
}
}
or, you can just simply use the permitAll() method to exclude the ones you need to allow anonymous access.
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.addFilter(mycustomFilter)
.authorizeRequests()
.antMatchers("/not-secured/**").permitAll()
.and()
.httpBasic();
}
}
I'm working on adding basic authentication to my RESTful web service (implemented using Spring MVC) with Spring Security having never really used it before. Right now I'm simply using an in-memory UserService with the intention of adding a repository-based one later.
<security:http>
<security:http-basic />
<security:intercept-url pattern="/**" access="ROLE_ADMIN" />
</security:http>
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="admin" password="admin"
authorities="ROLE_USER, ROLE_ADMIN" />
<security:user name="guest" password="guest"
authorities="ROLE_GUEST" />
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
This works fine, i.e. sending the following request grants me access to the desired resource (where the encoded string is admin:admin):
GET /user/v1/Tyler HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
And sending the following request gives me an Error 403 (where the encoded string is guest:guest):
GET /user/v1/Tyler HTTP/1.1
Authorization: Basic Z3Vlc3Q6Z3Vlc3Q=
However, sending a request where the provided username is not contained in the UserService does not result in an Error 403 as I expected (or at least desired) but instead continues prompting for a username and password. E.g. (where the encoded string is user:user):
GET /user/v1/Tyler HTTP/1.1
Authorization: Basic dXNlcjp1c2Vy
Is there additional configuration required to respond with an Error 403 when unrecognized user credentials are provided? How can I go about doing that?
Firstly,
403 Forbidden should be used when user is already authenticated but is not authorized to perform particular action. In your example guest is successfully authenticated but is not granted permission to see page because he is just a guest.
You should use 401 Unauthorized to indicate that your user was not authenticated successfully.
More on HTTP errors codes: http://en.wikipedia.org/wiki/HTTP_401#4xx_Client_Error
Secondly,
you can specify your custom behavior by extending BasicAuthenticationFilter.
There is protected void onUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse AuthenticationException failed) method that you can override and do whatever is adequate. In default implementation that method is empty.
Spring Security docs on injecting custom filter: CLICK
Edit:
What Spring Security does each time your authentication input is invalid:
public class BasicAuthenticationEntryPoint implements AuthenticationEntryPoint {
...
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
response.addHeader("WWW-Authenticate", "Basic realm=\"" + realmName + "\"");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
So, the default behavior is correct. User is sent 401 and is asked to provide valid login/credentials.
Before overriding, try to understand the default behavior. Source code: CLICK
You should try this in a client like wget or curl. You browser nags you several times for Basic redentials if your are rejected with 401. This is probably the case here.