I have a spring boot application (mvc) which is securing with keycloack. (using with spring-boot-starter-security and keycloak-spring-boot-starter)
I configured KeycloakWebSecurityConfigurerAdapter like that;
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Override
protected KeycloakAuthenticationProvider keycloakAuthenticationProvider() {
return this.tybsKimlikSaglayici;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.cors().and().authorizeRequests().antMatchers("/",
"/home").permitAll().antMatchers("/admin").permitAll()
.anyRequest().authenticated().and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/sso/logout")).permitAll();
http.exceptionHandling().accessDeniedPage("accessDeniedPage");
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(ImmutableList.of("*"));
configuration.setAllowedMethods(ImmutableList.of("HEAD",
"GET", "POST", "PUT", "DELETE", "PATCH"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(ImmutableList.of("Authorization", "Cache-Control", "Content-Type"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
Request to controller method which response html view works fine (keycloack authenticates request)
but,
Form action to controller method
Ajax request to rest controller method are not working (post, put, delete.. requests)
I added #CrossOrigin(origins = "*") to my controller.
Here is my ajax reqeust,
$.ajax({
type : "method_here",
contentType : "application/json; charset=utf-8;",
url : "url_here",
data : JSON.stringify(data),
timeout : 30000,
success : function(response) {
},
error : function(error) {
}
});
here is keycloack client
enter image description here
here is the kecloack json (i tryed application.properties file)
{
"realm": "demo-realm",
"auth-server-url": "url_of_sso_app",
"ssl-required": "external",
"resource": "kie-remote",
"principal-attribute": "preferred_username",
#"enable-cors": true, **tryed to add**
#"cors-max-age" : 10000,
#"cors-allowed-methods": "GET, POST, PUT, HEAD, OPTIONS",
#"cors-allowed-headers": "Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headersl",
"credentials": {
"secret": "secret_of_realm_client"
}
}
how can I fix this issue. How can i authenticate ajax request help with keycloack.
i found my missing.
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.POST, "/**")
.antMatchers(HttpMethod.PUT, "/**")
.antMatchers(HttpMethod.DELETE, "/**");
}
this method must be override in web security config. And it must be change like .antMatchers(HttpMethod.POST, "/allowed_method_path")
Edit:
This code ignoring authentication process for these http method types. Right solution does not use web.ignoring() method. Issue is related with csrf, (default spring security setting of csrf is enable) spring prevents put, delete, post http methods for to protect server from csrf attacks. If service does not consumes on browser, csrf can be disable, but service is producing a browser page, solution is that to configure csrf. Please check How to obtain csrf token in a velocity macro when using spring security
Thanks
Related
I'm having an issue trying to get Microprofile JWT working for my REST resources.
I'm able to build a JWT so when decoded on jwt.ie it is
{
"kid": "Oh1YDQopers_qYMU4zQCmAf0UsFVD5D0NmkFE79s2q0",
"typ": "JWT",
"alg": "RS256"
}
{
"token_type": "Bearer",
"sub": "user12",
"upn": "user12",
"groups": [
"ADMIN",
"USER"
],
"jti": "a27582fc-21e2-4365-b485-ed7193606d8b",
"iss": "http://www.testissuer.com",
"exp": 1617226928,
"iat": 1617219728
}
My Application class is annotated with
#LoginConfig(authMethod = "MP-JWT")
#DeclareRoles({"USER", "SUPERUSER", "ADMIN"})
#ApplicationPath("/")
public class MyApplication extends Application {
Resource class is annotated with
#Path("/my")
#PermitAll
#RequestScoped
public class MyResource {
#Inject
#Claim(standard = Claims.groups)
private Set<String> groups;
#GET
#Produces(MediaType.TEXT_PLAIN)
#RolesAllowed("USER")
public String getString() {
if (groups != null) {
return "groups.size(): " + groups.size();
}
else {
return "groups is null";
}
}
}
The ear file which includes this war includes a META-INF/microprofile-config.properties file with entry:
mp.jwt.verify.issuer=http://www.testissuer.com
To test this I'm generating a fresh token and setting the Authorization header to the encoded JWT and calling GET /my which is returning a 401 response. Added Bearer before the encoded token makes no difference. The WWW-Authenticate header on the 401 response looks like it's looking for a Basic realm value.
If I take out the #RolesAllowed("USER") line then the response I get back is "groups is null" so it's like the injection is failing or cannot be mapped to the "groups" claim in the JWT.
Anyone run into this before?
#PermitAll annotation on resource class was wrong, using it prevents principal data from being injected. Fix was to use #DenyAll on class and #RolesAllowed or #PermitAll at method level.
I have a Jersey REST interface that I want to send cookies in its responses to each request but no cookies are ever present in any response.
I have a feeling it's a CORS issue and I'm not sure what CORSResponseFilter configuration I need to allow cookies to be set and successfully sent in a response.
My project is built with SpringBoot under Kotlin.
Response code:
#POST
fun put(): Response {
val cookie = NewCookie(Cookie("RESERVATION", "TEST"),
"Session", 60 * 60, false)
return ok()
.cookie(cookie)
.entity(Result("OK", "Success"))
.build()
}
Filter code:
open class CORSResponseFilter : ContainerResponseFilter {
override fun filter(req: ContainerRequestContext?, res: ContainerResponseContext?) {
res?.headers?.add("Access-Control-Allow-Origin", "*")
res?.headers?.add("Access-Control-Allow-Methods", "POST, GET")
res?.headers?.add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization")
res?.headers?.add("Access-Control-Expose-Headers", "Set-Cookie")
res?.headers?.add("Access-Control-Allow-Credentials", "true")
}
}
Requests are successful and the responses send otherwise as intended but all without any cookies. Prior to setting up the response filter I was able to receive cookies in each response.
Any help is appreciated.
im trying to integrate spring security with a custom angular 2 login, that is a specific endpoint of my app is protected with spring security, trying to access it will redirect to /login that is handled in angular 2. as things stands now i have no clue as to how to perform the login and grant access to the backend API once logged.
i am configuring spring security as follows:
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors().and()
.authorizeRequests()
.antMatchers("/api/someEndpoint/**")
.hasRole(ADMIN_ROLE).and().formLogin()
.loginPage("/login").and().logout();
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
as I had the default login everything worked fine, but I have found myself unable to create a working angular 2 login integration.
I tried the following code in angular 2 to no avail:
login(loginDetails:Object) {
console.log(loginDetails)
const headers = new Headers({ 'Content-Type': 'application/json' });
const options = new RequestOptions({ headers: headers });
const body = JSON.stringify(loginDetails);
console.log(headers);
console.log(body);
return this.http.post(this.loginUrl, body, options)
}
as far as I know spring security defaults for username and password variable names are "username" and "password", which i am sure are being passed in the request body so when passing some invalid user data like {"username":"admin", "password" : "pass"}I should be redirected to /login?error or something, and when successfully authenticated I should be redirected to /welcome and stay authenticated
I have the user and pass defined in my db and my custom userDetailsService checks against it
any answers, comments or questions are welcome
Once you're working with an API you've to use the HTTP Basic authentication.
It's also required to use HTTPS to prevent the main-in-middle attack.
To implement HTTP Basic with Angular the login service would look like this:
login (loginDetails: any): Observable<LoginResponse> { // custom class, may be empty for now
let headers = new Headers({
'Authorization': 'Basic ' + btoa(loginDetails.login + ':' + loginDetails.pass),
'X-Requested-With': 'XMLHttpRequest' // to suppress 401 browser popup
});
let options = new RequestOptions({
headers: headers
});
return this.http.post(this.loginUrl, {}, options)
.catch(e => this.handleError(e)); // handle 401 error - bad credentials
}
... then you subscribe this in the caller component:
loginNow() {
this
.loginService
.login(this.loginDetails)
.subscribe(next => {
this.router.navigateByUrl("/"); // login succeed
}, error => {
this.error = "Bad credentials"; // or extract smth from <error> object
});
}
Then you can use the loginNow() method inside component templates like (click)="loginNow().
As soon as the server will accept an authorization, JSESSIONID will be stored in your browser automatically because of Spring Security features and you won't be forced to send the credentials each time you access private resources.
Your login server method may look like this:
#PreAuthorize("hasRole('USER')")
#PostMapping("/login")
public ResponseEntity login() {
return new ResponseEntity<>(HttpStatus.OK);
}
... it would reject with 401 UNAUTHORIZED when the authorization fails or accept with 200 SUCCESS when it's not.
How to setup a server in the proper way there's a number of Spring Security demo projects present: https://github.com/spring-guides/tut-spring-security-and-angular-js
Your spring security config needs to look like this
http!!
.cors().and()
.csrf().disable()
.authorizeRequests()
.requestMatchers(object: RequestMatcher {
override fun matches(request: HttpServletRequest?): Boolean {
return CorsUtils.isCorsRequest(request)
}
}).permitAll()
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll()
.and()
.formLogin().permitAll()
I had a similar issue, but I had to override the successlogout handler as mentioned here.
I'm trying to implement integration test using RestAssured library and Spring MVC REST oAuth2 secured endpoint.
This is my test:
#Test
public void testCreateDecision() throws Exception {
File createDecisionJsonFile = ResourceUtils.getFile(getClass().getResource("/json/decisions/create-decision.json"));
// #formatter:off
final String createDecisionRequest = FileUtils.readFileToString(createDecisionJsonFile)
.replace("{{name}}", "Test decision name")
.replace("{{description}}", "Test decision description");
// #formatter:on
String accessToken = getAccessToken("user", "user");
// #formatter:off
given()
.auth()
.oauth2(accessToken, OAuthSignature.HEADER)
.body(createDecisionRequest)
.contentType("application/json; charset=UTF-8")
.when()
.post(format("http://localhost:%d/api/v1.0/decisions/create", port))
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("id", notNullValue())
.body("createDate", notNullValue());
// #formatter:on
}
The accessToken is valid but I'm continuously getting 401 http code.
What could be wrong with my code ?
I know this is an old post, but just wanted to document this in case someone else needed the answer.
I was able to implement using the following format:
First retrieve the token (in my case I did not store user tokens, jut got them before each test)
// we need to get the oauth token before we can perform the request
private void authenticateUser(String username, String password) {
String response =
given()
.parameters("username", username, "password", password,
"grant_type", "password", "scope", "read write",
"client_id", "clientapp", "client_secret", "123456")
.auth()
.preemptive()
.basic("clientapp","123456")
.when()
.post("/oauth/token")
.asString();
JsonPath jsonPath = new JsonPath(response);
accessToken = jsonPath.getString("access_token");
}
And them on the test I used the retrieved token:
#Test
public void testGetUserDefaultUserOwner() {
authenticateUser(testData.user1.getLogin(), "1");
User user =
given()
.auth().oauth2(accessToken)
.contentType(ContentType.JSON)
.accept(ContentType.JSON)
.expect()
.log().all()
.statusCode(HttpStatus.OK.value())
.when()
.get(USER_RESOURCE, testData.user1.getId())
.as(User.class);
assertThat(user).isEqualTo(testData.user1);
}
I am using Restassured and AssertJ for the tests, and SpringBoot with OAuth2 for the Rest APIs.
I have reimplemented my test using OAuth2RestTemplate:
ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails();
resourceDetails.setUsername("user");
resourceDetails.setPassword("user");
resourceDetails.setAccessTokenUri(format("http://localhost:%d/oauth/token", port));
resourceDetails.setClientId("clientapp");
resourceDetails.setClientSecret("123456");
resourceDetails.setGrantType("password");
resourceDetails.setScope(asList("read", "write"));
DefaultOAuth2ClientContext clientContext = new DefaultOAuth2ClientContext();
OAuth2RestTemplate auth2RestTemplate = new OAuth2RestTemplate(resourceDetails, clientContext);
auth2RestTemplate.setMessageConverters(asList(new MappingJackson2HttpMessageConverter()));
Assert.assertNotNull(auth2RestTemplate.getAccessToken());
DecisionRequest decisionRequest = new DecisionRequest(name, description, parentDecisionId);
auth2RestTemplate.postForObject(format("http://localhost:%d/api/v1.0/decisions/create", port), decisionRequest, Decision.class);
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>302 Found</title>
</head><body>
<h1>Found</h1>
<p>The document has moved here.</p>
</body></html>
I'm using Grails version 2.4.3 . I am creating an application that supports RESTful APIs. Since access to these APIs should be authenticated , I tried out the Spring Security REST plugin. I checked out this example and what I could understand is , the /api/login controller is the authentication point which receives the user credentials in JSON format and after successful authentication it provides the acces token as response. I tried sending a POST request to /api/login/ with valid JSON data using the POSTMAN Rest Client. But it gives me the following error.
401 Unauthorized , Similar to 403 Forbidden, but specifically for use when authentication is possible but has failed or not yet been provided. The response must include a WWW-Authenticate header field containing a challenge applicable to the requested resource.
I also tried using IntellijIDEA's REST Client but doesn't work.
Then i tried by sending AJAX Request to /api/login/ with valid JSON data
, but getting 401 on console. What is the problem here? Is this the correct login end point? How can i get authenticated using JQuery?
Try this
$.ajax({
url: " http://localhost:8080/AppName/api/login",
type: "POST",
crossDomain: true,
data: JSON.stringify({"username":"yourusername" , "password":"yourpassword"}),
contentType: 'application/json; charset=utf-8',
dataType: "json",
success: function (response) {
console.log(response);
},
error: function (xhr, status) {
alert("error");
}
}) });
You can try this code for authentication,I am sending user id and password in request header you can try as you wish :-
inject following services:-
def springSecurityService
def authenticationManager
and use following code
def login = {
final String authorization = request.getHeader("Authorization");
if (authorization != null && authorization.startsWith("Basic")) {
boolean authResult = authenticateUser(authorization)
if (authResult) {
render response.status
} else {
render authFailed(response)
}
} else {
render authFailed(response)
}
}
protected boolean authenticateUser(String authorization) {
// Authorization: Basic base64credentials
def base64Credentials = authorization.substring("Basic".length()).trim();
byte[] credentials = base64Credentials.decodeBase64()
String actualCredential = new String(credentials)
// credentials format like username:password
final String[] values = actualCredential.split(":", 2);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(values[0], values[1]);
try {
def authentication = authenticationManager.authenticate(authRequest);
def securityContext = SecurityContextHolder.getContext();
securityContext.setAuthentication(authentication);
def session = request.session;
session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
}
catch (BadCredentialsException exception) {
return false
}
return true
}
protected HttpServletResponse authFailedResponse(HttpServletResponse response) {
response.setStatus(401)
response.setHeader("WWW-Authenticate", "Basic realm=\"nmrs_m7VKmomQ2YM3:\"")
return response;
}