Code:
package com.test.controllers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;
#RestController
#RequestMapping("/")
public class Controller {
private HttpServletRequest request;
public HttpServletRequest getRequest() {
return request;
}
#Autowired
public void setRequest(HttpServletRequest request) {
this.request = request;
}
#RequestMapping("safe-read")
public void threadSafeRead() throws InterruptedException {
System.out.println(request.getHeader("user-agent"));
Thread.sleep(TimeUnit.MILLISECONDS.convert(5,TimeUnit.SECONDS));
System.out.println(request.getHeader("user-agent"));
}
}
When I do two request in same time ,result of this execution is :
Mozilla/5.0 (Windows NT 6.1; WOW64; rv:26.0) Gecko/20100101
Firefox/26.0
Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like
Gecko) Chrome/32.0.1700.102 Safari/537.36
Mozilla/5.0 (Windows NT 6.1; WOW64; rv:26.0) Gecko/20100101
Firefox/26.0
Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like
Gecko) Chrome/32.0.1700.102 Safari/537.36
In runtime field have type com.sun.proxy.$Proxy45.
How spring make it thread-safe for read?
Just to expand a little on the comment by M. Deinum above, there are a few approaches you could use to avoid holding a reference to the request as part of the controller's state.
1) As already mentioned, directly inject the request as part of the method signature.
2) Only work with the request params that you are directly interested in by using the #RequestParam annotations in your method signatures.
3) Take a look at the Spring api doc for the RequestContextHolder class. You can do things like:
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.currentRequestAttributes()).getRequest();
which may or may not be useful to you.
Hope this helps :-)
Related
I am currently working on an application with spring, and I currently face the problem that all requests I do return the error 403 - Forbidden. It is not only [post,put,patch,delete], but also get. Also, I have csrf already disabled.
Here my SecurityConfig:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.anonymous().and()
.authorizeRequests()
.antMatchers("/signin").permitAll()
.antMatchers("/signup").permitAll()
.antMatchers("/rss/feed").permitAll()
.antMatchers("/article/{guid}").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint).and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.csrf().disable().cors();
}
And here one of the endpoints which is not working properly:
#PreAuthorize("hasAnyRole('ADMIN', 'PUBLISHER', 'USER')")
#GetMapping("/users/current")
public User getCurrent(#RequestHeader Map<String, String> headers){
String token = headers.get("Authorization");
System.out.println("Current user request");
return userAuthService.getUserByUsername(jwtUtil.getUser(token).getUsername());
}
And yes, I know that csrf().disable() is dangerous, I disabled it for now to see if it is some problem with csrf.
Here my JwtAuthenticationFilter:
#Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private JwtUtil jwtUtil;
#Autowired
private UserAuthService userAuthService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String header = request.getHeader("Authorization");
if(header == null){
throw new NullPointerException("No headers");
}
if (!header.startsWith("Bearer")) {
throw new JwtTokenMissingException("No JWT token found in the request headers");
}
String token = header.substring(7);
// Optional - verification
jwtUtil.validateToken(token);
UserVo userVo = jwtUtil.getUser(token);
UserDetails userDetails = userAuthService.loadUserByUsername(userVo.getUsername());
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
if (SecurityContextHolder.getContext().getAuthentication() == null) {
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
filterChain.doFilter(request, response);
}
}
Edit: I enabled Security logging and got the following error on Spring console:
2022-04-04 18:22:26.611 DEBUG 9804 --- [nio-8080-exec-2] o.s.s.a.i.a.MethodSecurityInterceptor : Failed to authorize ReflectiveMethodInvocation: public at.brigot.kainblog.pojos.User at.brigot.kainblog.controller.AuthController.getCurrent(java.util.Map); target is of class [at.brigot.kainblog.controller.AuthController] with attributes [[authorize: 'hasAnyRole('ADMIN', 'PUBLISHER', 'USER')', filter: 'null', filterTarget: 'null']]
Also if needed, here the full request info I got from spring:
************************************************************
Request received for GET '/users/current':
org.apache.catalina.connector.RequestFacade#1d49a6ca
servletPath:/users/current
pathInfo:null
headers:
host: localhost:8080
connection: keep-alive
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="98", "Opera GX";v="84"
accept: application/json, text/plain, */*
authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJnb3RwZWQxNyIsInJvbGVzIjpbIkFETUlOIiwiVVNFUiJdLCJpYXQiOjE2NDkwODkyMDcsImV4cCI6MTY0OTA4OTM4N30.b5vg-azO433Ozk8GoiakQC-T2ULdFVsde6MrJhW8XpIhA5k5AtA_Q6i0vuCGATQV8RwteMzBc86CzKmuQ7kuYA
sec-ch-ua-mobile: ?0
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36 OPR/84.0.4316.52
sec-ch-ua-platform: "Windows"
origin: http://localhost:3000
sec-fetch-site: same-site
sec-fetch-mode: cors
sec-fetch-dest: empty
referer: http://localhost:3000/
accept-encoding: gzip, deflate, br
accept-language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7
Security filter chain: [
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CorsFilter
LogoutFilter
JwtAuthenticationFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]
************************************************************
I am trying to call the API in Backend but I have some error that I have not an idea from what is causing by.
The problem started after I config the spring security in the backend.
The call should activate Preflighted requests OPTION
In my backend file, I have
#Configuration
#EnableWebSecurity
public class SpringSecurityConfigurationBasicAuth extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS,"/**").permitAll()
.anyRequest().authenticated()
.and()
// .formLogin().and()
.httpBasic();
}
}
and in the frontend, I have this part of the code.
executeHelloWorldServiceWithPathVariable(name) {
const basicAuthHeaderString = this.createBasicAuthenticationHttpHeader();
const headers = new HttpHeaders({
Authorization: basicAuthHeaderString
});
return this.http.get<HelloWorldBean>(`http://localhost:8080/hello-world/path-variable/${name}`,
{headers});
}
createBasicAuthenticationHttpHeader() {
const username = 'start';
const password = 'end';
const basicAuthHeaderString = 'Basic ' + window.btoa(username + ':' + password);
return basicAuthHeaderString;
}
In the backend, I have already include
#CrossOrigin(origins = "http://localhost:4200")
but still, I am not able to call this API
in the console, I should get something like an OPTION method but in fact, I get those:
General
Request URL: http://localhost:8080/hello-world/path-variable/start
Referrer Policy: no-referrer-when-downgrade
Response Header
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Connection: keep-alive
Content-Length: 0
Date: Tue, 28 Jan 2020 11:11:49 GMT
Expires: 0
Keep-Alive: timeout=60
Pragma: no-cache
WWW-Authenticate: Basic realm="Realm"
WWW-Authenticate: Basic realm="Realm"
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
request head
Accept: application/json, text/plain, /
Accept-Encoding: gzip, deflate, br
Accept-Language: en,cs;q=0.9,en-US;q=0.8
Authorization: Basicc3RhcnQ6ZWVuZA==
Connection: keep-alive
Host: localhost:8080
Origin: http://localhost:4200
Referer: http://localhost:4200/welcome/start
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
and in console, I see this error
Try to add cors policy in your security configuration:
#Configuration
#EnableWebSecurity
public class SpringSecurityConfigurationBasicAuth extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.cors();
http.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS,"/**").permitAll()
.anyRequest().authenticated()
.and()
// .formLogin().and()
.httpBasic();
}
}
to the class SpringSecurityConfigurationBasicAuth try to add this method
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
Got a websocket - see the authToken in the Cookie, in Java Spring how do you validate this authToken? I understand this authToken is passed down from the http layer to the websocket so I'm trying to validate that the websocket is being opened by our app and not by some other source.
Headers for Websocket:
GET ws://localhost:9999/somePath/websocket HTTP/1.1
Host: localhost:9999
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36
Upgrade: websocket
Origin: http://localhost
Sec-WebSocket-Version: 13
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: authToken=elFKMk5TckR0ZUNvdnZySUJxc2ZMdz09OklEZENrRFRySkp0U0ltVFdKU1RIZVE9PQ
Sec-WebSocket-Key: e//VDAjHSRjE810tCbIEyw==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Protocol: v10.stomp, v11.stomp, v12.stomp
I would like to validate that authToken in the HttpHandshakeInterceptor.beforeHandshake
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer
registry.addEndpoint(stompEndPoint).addInterceptors(new HttpHandshakeInterceptor()).setAllowedOrigins("*").withSockJS();
public class HttpHandshakeInterceptor implements HandshakeInterceptor
Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpSession session = servletRequest.getServletRequest().getSession();
attributes.put("sessionId", session.getId());
// validate token logic
}
return true;
}
I'm trying to implement basic HTTP authentication using Restlet 2.1, and I just can't get it to work .. ! I'm using a ChallengeAuthenticator to setup Basic HTTP authentication. I have just one URI \test, for which I'm trying to get authentication working.
I build my code, then run it as a Web Application and then browse to http://localhost:8888/test to see if I get a prompt for a username/password, but I don't get any. I just get a blank screen.
Also when I browse to http://localhost:8888/test, I get the following written in Eclipse's Console:
WARNING: A response with a 200 (Ok) status should have an entity. Make sure that resource "http://localhost:8888/test" returns one or sets the status to 204 (No content).
When when I browse to http://user:password#localhost:8888/test, the result is exactly the same.
The HTTP headers (from Chrome) are as follows:
Request:
GET /test HTTP/1.1
Host: localhost:8888
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8,ms;q=0.6
Response (no mention of Basic authentication):
HTTP/1.1 200 OK
Date: Fri, 13 Jun 2014 11:21:05 GMT
Accept-Ranges: bytes
Server: Development/1.0
Content-Length: 0
Cache-Control: no-cache
Expires: Fri, 01 Jan 1990 00:00:00 GMT
Here is the Java code:
package com.poc.hw7;
import org.restlet.*;
import org.restlet.data.ChallengeScheme;
import org.restlet.data.Cookie;
import org.restlet.data.MediaType;
import org.restlet.routing.Router;
import org.restlet.security.*;
import org.restlet.util.Series;
public class AuthTestApp extends Application {
private ChallengeAuthenticator authenticator;
private ChallengeAuthenticator createAuthenticator() {
Context context = getContext();
boolean optional = false;
ChallengeScheme challengeScheme = ChallengeScheme.HTTP_BASIC;
String realm = "Example site";
MapVerifier verifier = new MapVerifier();
verifier.getLocalSecrets().put("user", "password".toCharArray());
ChallengeAuthenticator auth = new ChallengeAuthenticator(context, optional, challengeScheme, realm, verifier) {
#Override
protected boolean authenticate(Request request, Response response) {
if (request.getChallengeResponse() == null) {
return false;
} else {
return super.authenticate(request, response);
}
}
};
return auth;
}
#Override
public Restlet createInboundRoot() {
this.authenticator = createAuthenticator();
Restlet hw_restlet = new Restlet(getContext())
{
public void handle(Request request, Response response)
{
String message = "Hello World!";
response.setEntity(message,MediaType.TEXT_PLAIN);
}
};
Router router = new Router();
router.attach("/test", hw_restlet);
authenticator.setNext(router);
return authenticator;
}
public boolean authenticate(Request request, Response response) {
if (!request.getClientInfo().isAuthenticated()) {
authenticator.challenge(response, false);
return false;
}
return true;
}
}
Here is the web.xml file:
<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<display-name>Restlet URI Rewrite</display-name>
<servlet>
<servlet-name>RestletServlet</servlet-name>
<servlet-class>org.restlet.ext.servlet.ServerServlet</servlet-class>
<init-param>
<param-name>org.restlet.application</param-name>
<param-value>com.poc.hw7.AuthTestApp</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>RestletServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
Can someone please tell me how to get basic HTTP authentication working ?
Your response was a 200 code, which means your server never asked for one. You would need to send a 401 response. For most web browsers, this will automatically pop up the basic-auth box for a username and password.
As for why this is happening... it's hard to say. restlet looks very... bad. There are easier frameworks to work with, where you can just annotate a function to listen to a certain URI and/or request method.
I am trying to send a json list from the controller to the javascript when the given URL is accessed. With this code, I am receiving a 406 error when I go to the URL. My servlet context has <annotation-driven /> and I have the org.codehaus.jackson maven dependencies. Is there any other reason this is giving me a 406, or is there a better way to go about sending a list to the javascript?
Controller Function
#RequestMapping(value = "/{room}/handhygiene.json", method = RequestMethod.GET)
public #ResponseBody ArrayList<Integer> getHandHygienePageAsync(
#PathVariable(value = "room") String roomCode) {
ArrayList<Integer> json = new ArrayList<Integer>();
Room room = getRoom(roomCode);
json.add(service.getHandHygieneEvents(room));
if (room.isGroupBased()) {
json.add(service.getRoomEntryExits(room));
}
if (room.isIdBased()) {
json.add(service.getPatientContacts(room));
}
return json;
}
Javascript
$.getJSON(currentURL + ".json",
function(data){
alert('I can\'t get here though');
var overallRatio;
var handWash = data[0];
$("#handwash").html(handWash);
if(groupBased == true)
{
var enex = data[1];
$("#enex").html(enex);
overallRatio = "" + Math.round((handWash*100)/enex) + "%";
}
else
{
if(idBased == true)
{
var contacts = data[2];
$("#contacts").html(contacts);
overallRatio = "" + Math.round((handWash*100)/contacts) + "%";
}
}
$("#overall").html(overallRatio);
}
);
Here's the headers for the request
GET /groupbased/ICU6/handhygiene.json HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.152 Safari/537.22
Referer: http://localhost:8080/groupbased/ICU6/handhygiene
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
Cookie: JSESSIONID=063CDE4BF5FB7369B6C48FC2EADFD8E9
Would this view resolver conflict with anything?
#Bean
public InternalResourceViewResolver internalResourceViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
Add below config to mvc-servet.xml
<bean name="customViewResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
<property name="location" value="/WEB-INF/config/spring/customViews.xml" />
<property name="order" value="0"/>
</bean>
Create below customViews.xml in the path /WEB-INF/config/spring/customViews.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<bean name="jsonView" class="org.springframework.web.servlet.view.json.JsonView"/>
</beans>
controller change:-
#RequestMapping(value = "/{room}/handhygiene.json", method = RequestMethod.GET)
public #ResponseBody String getHandHygienePageAsync(
#PathVariable(value = "room") String roomCode,ModelMap model) {
ArrayList<Integer> json = new ArrayList<Integer>();
Room room = getRoom(roomCode);
json.add(service.getHandHygieneEvents(room));
if (room.isGroupBased()) {
json.add(service.getRoomEntryExits(room));
}
if (room.isIdBased()) {
json.add(service.getPatientContacts(room));
}
model.addAttribute("json",json)
return "jsonView";
}
Feel free to mark it as answer and to click uplink, if this solved your problem.