I am new to Keycloak, I am using the official tutorial project on
https://github.com/sebastienblanc/spring-boot-keycloak-tutorial
for integrating with Springboot application, I have setup the KeyCloak server successfully and the spring boot application also directing to the client application I have created on the Realm I have created on KeyCloak, after providing the correct credentials it directs to the forbidden page.
#Controller
class ProductController {
#GetMapping(path = "/products")
public String getProducts(Model model){
model.addAttribute("products", Arrays.asList("iPad","iPhone","iPod"));
return "products";
}
#GetMapping(path = "/logout")
public String logout(HttpServletRequest request) throws ServletException {
request.logout();
return "/";
}
}
Application.properties file
keycloak.auth-server-url=http://localhost:8080/auth
keycloak.realm=springdemo
keycloak.resource=product-app
keycloak.public-client=true
keycloak.security-constraints[0].authRoles[0]=testuser
keycloak.security-
constraints[0].securityCollections[0].patterns[0]=/products/*
server.port=8081
I am not getting any error message from KeyCloak console or spring embedded tomcat console.
Check the tomcat console here - no error
Thank you.
I think you have a typo at
keycloak.security-constraints[0].authRoles[0]=testuser , you should specify the role here and not the user.
If you follow the blogpost instructions it should be :
keycloak.security-constraints[0].authRoles[0]=user
In my case here I set use-resource-role-mappings to true, considering that it would provide both realm and client roles, but it turns out that if this option is set to true, only client roles are considered.
AFAICS, there is no way to use both.
I had the same issue and the problem was that I was using variables separated by dashes, instead of camel case. For example,
I had this (incorrect):
keycloak:
auth-server-url: http://localhost:8083/auth
realm: springdemo
resource: Resource_Name
public-client: true
security-constraints[0].auth-roles[0]: user
security-constraints[0].security-collections[0].patterns[0]: /
instead of (correct):
keycloak:
authServerUrl: http://localhost:8083/auth
realm: springdemo
resource: Resource_Name
publicClient: true
securityConstraints[0].authRoles[0]: user
securityConstraints[0].securityCollections[0].patterns[0]: /
I have tried this Week End to replay the example from the very interesting DEvoxx Sebastien speak.
I had the same 403 error with the role "user" specified in the property
keycloak.security-constraints[0].authRoles[0]=user
The "user" role does not exists in the default keycloak configuration. You have to create it before in your realm (realm/configuration/roles) and assign it to your user (realm/users/user/roles mappings).
About that tutorial, I just have a problem with logout feature.
Sometimes the logout does not work.
1) I click on logout and then I click on /products, then I am not redirected to keycloak login page
2) If I click on logout, then I refresh the browser page, then I click on /products I am redirected to the keycloak login page.
It seams to be that the logout implementation from HttpServletRequest is not enough to really logout the user ?
`
#GetMapping(path = "/logout")
public String logout(HttpServletRequest request) throws ServletException{
request.logout();
return "/";
}
`
If somebody has an explanation on that behavior between springboot and keycloak. Thank you.
Late to the party, but this might help someone.
In my case, I had resource authorization enabled (so client was not public). I had to do the following
Under Client
Authorization -> Settings -> Policy Enforcement Mode
Set it to "Permissive"
In my case I have to turn off Client Authentication and Authorization (both) in client config.
Related
I am building a Spring Cloud gateway and trying to logout keycloak but it is giving me cors errors, my code it as below:
Security class in which I defined logout code logic:
#Bean
public ServerSecurityContextRepository securityContextRepository() {
WebSessionServerSecurityContextRepository securityContextRepository =
new WebSessionServerSecurityContextRepository();
securityContextRepository.setSpringSecurityContextAttrName("langdope-security-context");
return securityContextRepository;
}
private LogoutWebFilter logoutWebFilter() {
LogoutWebFilter logoutWebFilter = new LogoutWebFilter();
SecurityContextServerLogoutHandler logoutHandler = new SecurityContextServerLogoutHandler();
logoutHandler.setSecurityContextRepository(securityContextRepository());
RedirectServerLogoutSuccessHandler logoutSuccessHandler = new RedirectServerLogoutSuccessHandler();
logoutSuccessHandler.setLogoutSuccessUrl(URI.create("http://localhost:9000/app/Default"));
logoutWebFilter.setLogoutHandler(logoutHandler());
logoutWebFilter.setLogoutSuccessHandler(logoutSuccessHandler);
logoutWebFilter.setRequiresLogoutMatcher(
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/app/logout")
);
return logoutWebFilter;
}
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http,ReactiveClientRegistrationRepository repository) {
// Authenticate through configured OpenID Provider
http.addFilterAfter(new CustomWebFilter(), SecurityWebFiltersOrder.LAST).authorizeExchange()
.pathMatchers("/app/logout").permitAll()
.pathMatchers("/app/authenticate").authenticated()
.pathMatchers("/app/**").authenticated().and().
logout().disable()
.securityContextRepository(securityContextRepository())
.addFilterAt(logoutWebFilter(), SecurityWebFiltersOrder.LOGOUT)
.oauth2Login(Customizer.withDefaults());
// Also logout at the OpenID Connect provider
http.httpBasic().disable();
// Require authentication for all requests
// http.authorizeExchange().anyExchange().authenticated();
// Allow showing /home within a frame
http.headers().frameOptions().mode(XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN);
// Disable CSRF in the gateway to prevent conflicts with proxied service CSRF
http.csrf().disable();
return http.build();
}
Now when from front-end I hit logout it gives me below error:
Access to XMLHttpRequest at 'http://localhost:8280/auth/realms/Default/protocol/openid-connect/auth?response_type=code&client_id=Default&scope=openid%20email%20profile&state=qVQ46iGilTo9o2Ro7CdZzl9kmsMm23jnEqckybucgII%3D&redirect_uri=http://localhost:9000/login/oauth2/code/keycloak&nonce=Z6hMnfYEJaOpuJnX44obCe6GyW8Oc6FSn3MOU_2bRg4' (redirected from 'http://localhost:9000/app/logout') from origin 'http://localhost:9000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
In Keycloak for valid URL I have given * to test but still not working. What am I missing?
I've spent my share of time figuring out keycloak CORS errors and this is what I figured out.
If you have web origins correctly configured (https://stackoverflow.com/a/59072362/20992932) and you are still getting CORS errors there is high probability that the request you are sending is incorrect (jboss handles error before checking client web origin).
To find out if that is the case for you the easiest solution would be to disable same origin policy on the browser (of course for the time of the testing only). Then in network console you should see the actual error response.
Here is how to do it in chrome based browsers:
chromium-browser --disable-web-security --user-data-dir="[some directory here]"
For more see:
https://stackoverflow.com/a/59072362/20992932
I'm one of the developers of an application in what we have a login page to enter the dashboard. Inside the dashboard, there are different links that send you to other parts of the application.
The problem comes when you want to enter one of the internal links when you are no login. so in the browser you enter the address, put your credentials to continue to the web page, but instead, you are redirected to the dashboard.
The webpage I want to enter is:
http://localhost:8080/admin-ng/index.html#!/events/events/cd15fb84-d6b1-49ab-a28c-e5e07683907e/tools/editor
but after login always redirects to the dashboard:
http://localhost:8080/admin-ng/index.html#!/events/events
Then if I enter again the address works.
Also, I tried with a rest endpoint to call again the address after you log in, but it doesn't work, it is the same behavior.
I think it should be with the spring security config file. In how handle the requests after you are log in. I think it loses the rest of the information after the "#". What I can do?
Thanks
Maybe you could add a success handler bean:
.loginPage(LOGIN_FORM_URL)
.loginProcessingUrl("/login")
.successHandler(successHandler())
...
#Bean
public SavedRequestAwareAuthenticationSuccessHandler successHandler() {
SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successHandler.setTargetUrlParameter("myredirectparam");
successHandler.setDefaultTargetUrl("mydefaultredirect");
return successHandler;
}
The problem comes from your authentication success handler, or your entry point.
I suggest to debug to figure out what is put in the INITIAL_REQUEST_PATH session attribute and if it is what you want.
I'm trying to enable role based access control on a rest end point that I've setup using undertow, jersey and CDI. I initialize the servlet deployment as follows:
DeploymentInfo servletBuilder = Servlets.deployment()
.setClassLoader(Main.class.getClassLoader())
.setContextPath("/rest")
.setDeploymentName("sv.war")
.addListeners(listener(Listener.class))
.setLoginConfig(new LoginConfig("KEYCLOAK", "some-realm"))
.setAuthorizationManager(auth) // my dummy for testing
.addServlets(servlet("jerseyServlet", ServletContainer.class)
.setLoadOnStartup(1)
.addInitParam("javax.ws.rs.Application", SystemViewApplication.class.getName())
.addMapping("/api/*"));
I enabled kecloak authentication based on this example code.
So, my server is started as:
DeploymentManager manager = Servlets.defaultContainer().addDeployment(servletBuilder);
manager.deploy();
PathHandler path = Handlers.path(Handlers.resource(staticResources).setDirectoryListingEnabled(false).setWelcomeFiles("index.html"))
.addPrefixPath("/rest", manager.start());
Undertow server = Undertow.builder()
.addHttpListener(8087, "localhost")
.setHandler(sessionHandling(addSecurity(exchange -> {
final SecurityContext context = exchange.getSecurityContext();
if (!context.isAuthenticated()) {
exchange.endExchange();
return;
}
log.info("Authenticated: {} {} {}", context.getMechanismName(), context.getAuthenticatedAccount().getPrincipal().getName(), context.getAuthenticatedAccount().getRoles());
// propagate the request
path.handleRequest(exchange);
})))
.build();
server.start();
Where the two methods sessionHandling() and addSecurity() are lifted from the example I've linked above.
The authentication works, I am forced to log in, and the Authenticated: .. logging line is printed out with the correct details. But, once it hits the servlet handling, the security context (and account) is lost. I've traced this call and I can see that at some point along the path, it's replaced by brand new SecurityContext which has a null account.
Now my question - is there some authentication mechanism that I am missing that would propagate the state after the keycloak authentication or can I just fix the undertow code and in the SecurityContext, if the passed in context is already correctly authenticated, accept that state and move on? (the latter doesn't seem right, I'm guessing it's because the could be different authentication for the servlet deployment?) If so, is there any way to connect the servlet deployment to see the keycloak authentication has already happened?
Incase anyone comes looking here on how to authenticate servlets properly with keycloak and use role based authentication, this worked for me (note, this worked for me without the requirement of any xml files, purely with annotations.
First in the servlet application (wherever you extended ResourceConfig) register() the RolesAllowedDynamicFeature.class.
Also enable "use-resource-role-mappings": true in keycloak.json.
Next, instantiate the servlet deployment with an initial security wrapper:
DeploymentInfo servletBuilder = Servlets.deployment()
.setClassLoader(Main.class.getClassLoader())
.setContextPath("/")
.setDeploymentName("sv.war")
.addListeners(listener(Listener.class))
.setIdentityManager(idm)
.setSessionManagerFactory(new InMemorySessionManagerFactory())
.setInitialSecurityWrapper(handler -> sessionHandling(addSecurity(handler)))
.setResourceManager(staticResources)
.addWelcomePage("index.html")
.addServlets(servlet("jerseyServlet", ServletContainer.class)
.setLoadOnStartup(1)
.addInitParam("javax.ws.rs.Application", SystemViewApplication.class.getName())
.addMapping("/api/*"));
DeploymentManager manager = Servlets.defaultContainer().addDeployment(servletBuilder);
manager.deploy();
Undertow server = Undertow.builder()
.addHttpListener(8087, "localhost")
.setHandler(Handlers.path(manager.start()))
.build();
server.start();
Where sessionHandling(addSecurity(handler)) is basically the code from the linked github repo.
Now authentication via keycloak will work, and also role based authentication will work, so for example, if you have a CDI injected rest end point, such as:
#RolesAllowed({"admin", "guest"})
#GET
#Path("/{id}")
public Response findById(#PathParam("id") #NotNull Integer id){
// some method
}
As long as the roles are configured in keycloak, it should work.
I am using:
Spring 4.1.1.RELEASE
Spring Security 3.2.5.RELEASE
spring-security-oauth2 1.0.0.RELEASE
I have created a two multipart request:
One is at Non-secure controller Second at a Secure controller.
Both are same, there is no change.
Non secured request works very fine but secured Multipart request not working
#RequestMapping(value="/profileimage", method=RequestMethod.POST)
public #ResponseBody String createProfilePicture(#RequestParam MultipartFile
file, #RequestParam String profileId){
}
Please reply if you require more information.
Please specify what exactly you are trying to achieve by implementing an oauth2 authorization server.
Do you plan to support various authentication providers e.g Facebook, Linkedin, Google?
Please specify the error message you get when trying to consume the API.
Please share your Security configuration classes i.e the class that extends WebSecurityConfigurerAdapter and any other configuration that you have.
You can review this article which details how to implement an oauth2 authorization server to make sure you haven't missed any part.
In regards to the code above, it seems corrent but can be simplified:
#PostMapping("/profileimage")
public #ResponseBody String createProfilePicture(#RequestParam MultipartFile
file, #RequestParam String profileId){
}
You can even drop the #ResponseBody annotation in case a #RestController
annotation is specified on the class.
Good luck!
I'm familiar with implementing BasicAuth security in Dropwizard, but only for RESTful endpoints/resources.
I am now experimenting with Dropwizard Views to see if I can use it to be both a web and REST server all in one. The web app will have "public" pages (that are really just static HTML files; "About Us", "Contact Us", etc.) as well as "private" (dynamic) pages, which really form the app. To get to these pages the user must be authenticated (logged in).
So this means I have the need for two distinct DW authentication mechanisms:
A typical DW security mechanism for authenticating REST API clients, which I'm familiar with; and
A way to implement a log in system for authenticating end users for the web app pages/resources
Ideally, I'd like Apache Shiro to handle all auth for my system (REST and web alike), and I see the Dropwizard-Shiro lib, but that seems to only authenticate REST endpoints.
My web login system need to work like so:
A user tries to go to an "authenticated" (private) URL.
A servlet filter (that I create myself and register with the environment) intercepts the request and can tell (perhaps a cookie/session var?) whether the user is authenticated or not.
If the user is authenticated, they are allowed to proceed to their intended URL ("target URL"). Otherwise they are redirected to a login page. When they login a DW resource/controller hands their credentials off to Shiro, who then decides whether the credentials are valid or not.
If the credentials are valid, they get a cookie/session var (?) and are redirected to their target URL. Otherwise they are redirected back to the login page which will now display a failure message.
My main concerns are:
* What should I implement for the cookie/session var that the servlet will check for?; and
* How do I integrate my auth controller (that is, the resource that handles redirection between the login page and the target URL) with Shiro? Is it possible to do this via that Dropwizard-Shiro lib?
My best attempt thus far:
Custom Servlet Filter (registered with environment):
public class AuthFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) {
Cookie[] cookies = requestContext.getCookies();
boolean authenticated = false;
for(Cookie cookie : cookies) {
// 1. This is my first concern. What is better/more secure
// than what I'm doing here?
if("my_app_auth_cookie".equals(cookie.getName())) {
authenticated = true;
}
}
if(authenticated == false) {
responseContext.sendDirect("/auth/login");
}
}
}
If they are not authenticated they redirect to /auth/login which hits a AuthController (again, registered with the environment as a resource):
#Path("/auth")
#Produces(MediaType.TEXT_HTML)
public class AuthController {
#GET
#Path("/login")
public LoginPageView login() {
// Render some "login.ftl" template as HTML.
}
#POST
#Path("/authenticate")
public ??? authenticate(??? username, ??? password) {
// 2. Somehow send 'username' and 'password' to Shiro...
MyAppUser user = myAppRealm.authenticate(username, password);
// Now what do I do with 'user'?
}
}
When the user submits the form on the login page (which might be a POST to /auth/authenticate) we somehow hand their inputted credentials off to Shiro (again I'd like to use that Dropwizard-Shiro lib since I will likely also be using it for my REST endpoints).
Apache Shiro has its own type of filters which are configured in shiro.ini.
Example:
[urls]
/api/** = noSessionCreation, authcBasic
/views/login = authc
/views/authenticated = authc, user
Configure the authc filter to redirect to a login form page that you implement. Use the filter's form parameters and POST to /views/login.
If you enable the session manager in Jetty, Shiro should create servlet sessions when they log in from the login form page.
Please note that I haven't actually tested this configuration.