OAuth2 Spring security with Google login doesn't work - spring

I found a really good tutorial on third-party login with Spring security in the link below,
It contains both front-end and back-end code.
https://www.callicoder.com/spring-boot-security-oauth2-social-login-part-1/
Github : https://github.com/callicoder/spring-boot-react-oauth2-social-login-demo
Everything works perfectly locally as well as in a cloud server in a normal docker container.
But if I put this code behind a reverse proxy preferably Nginx (using docker swarm) there seems to be some issue in the redirection. In my console, I'll get the authorization_request_not_found error. I have basically googled and tried everything but for some reason, it's not working. I am definitely missing something. So any help is much appreciated. The required code snippets are down below.
For example, if our domain is test.oauth.com
i) Nginx proxy's default.conf
server {
listen 443 ssl;
index index.html index.htm index.nginx-debian.html;
server_name test.oauth.com;
ssl_certificate /home/bundle_chained.crt;
ssl_certificate_key /home/private.key;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header Cookie $http_cookie;
access_log /var/log/nginx/reverse-access.log;
error_log /var/log/nginx/reverse-error.log;
location /login {
rewrite ^/login(.*)$ $1 break;
proxy_pass http://test-login-server:8080/;
proxy_redirect off;
}
location / {
rewrite ^/(.*)$ /$1 break;
proxy_pass http://test-react-server/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection ‘upgrade’;
proxy_cache_bypass $http_upgrade;
}
}
server {
listen 80;
server_name test.oauth.com;
return 301 https://$host$request_uri;
}
ii) Nginx react server's default.conf
server {
listen 80;
index index.html index.htm index.nginx-debian.html;
server_name test.oauth.com;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
proxy_cache cache_zone;
proxy_cache_background_update on;
proxy_cache_lock on;
proxy_cache_use_stale error timeout http_500;
proxy_cache_valid 200 10m;
add_header X-Cached $upstream_cache_status;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
I am using another Nginx server (test-react-server) to serve the front-end build.
iii) Application.yml of Spring
server:
forward-headers-strategy: NATIVE
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/spring_social?createDatabaseIfNotExist=true
username: <username>
password: <password>
jpa:
show-sql: true
hibernate:
ddl-auto: update
naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
security:
oauth2:
client:
registration:
google:
clientId: <google-client-id>
clientSecret: <google-client-secret>
redirectUri: "{baseUrl}/login/oauth2/callback/{registrationId}"
scope:
- email
- profile
facebook:
clientId: <facebook-client-id>
clientSecret: <facebook-client-secret>
redirectUri: "{baseUrl}/login/oauth2/callback/{registrationId}"
scope:
- email
- public_profile
github:
clientId: <github-client-id>
clientSecret: <github-client-secret>
redirectUri: "{baseUrl}/login/oauth2/callback/{registrationId}"
scope:
- user:email
- read:user
provider:
facebook:
authorizationUri: https://www.facebook.com/v3.0/dialog/oauth
tokenUri: https://graph.facebook.com/v3.0/oauth/access_token
userInfoUri: https://graph.facebook.com/v3.0/me?fields=id,first_name,middle_name,last_name,name,email,verified,is_verified,picture.width(250).height(250)
app:
auth:
tokenSecret: 04ca023b39512e46d0c2cf4b48d5aac61d34302994c87ed4eff225dcf3b0a218739f3897051a057f9b846a69ea2927a587044164b7bae5e1306219d50b588cb1
tokenExpirationMsec: 864000000
cors:
#allowedOrigins: http://localhost:3000
allowedOrigins: https://test.oauth.com,https://test.oauth.com/login
oauth2:
authorizedRedirectUris:
#- http://localhost:3000/oauth2/redirect
- https://test.oauth.com/oauth2/redirect
iv) Front-end's package.json
{
"name": "react-social",
"version": "0.1.0",
"private": true,
"homepage": "http://test-react-server",
"dependencies": {
"react": "^16.5.2",
"react-dom": "^16.5.2",
"react-router-dom": "^4.3.1",
"react-s-alert": "^1.4.1",
"react-scripts": "1.1.5"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
v) Front-end's index.js
export const API_BASE_URL = 'https://test.oauth.com/login';
export const ACCESS_TOKEN = 'accessToken';
export const OAUTH2_REDIRECT_URI = 'https://test.oauth.com/oauth2/redirect'
export const GOOGLE_AUTH_URL = API_BASE_URL + '/oauth2/authorize/google?redirect_uri=' + OAUTH2_REDIRECT_URI;
export const FACEBOOK_AUTH_URL = API_BASE_URL + '/oauth2/authorize/facebook?redirect_uri=' + OAUTH2_REDIRECT_URI;
export const GITHUB_AUTH_URL = API_BASE_URL + '/oauth2/authorize/github?redirect_uri=' + OAUTH2_REDIRECT_URI;
vi) Stack trace of error
org.springframework.security.oauth2.core.OAuth2AuthenticationException: [authorization_request_not_found]
at org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter.attemptAuthentication(OAuth2LoginAuthenticationFilter.java:170)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:222)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.doFilterInternal(OAuth2AuthorizationRequestRedirectFilter.java:178)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:110)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:80)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:211)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:183)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
at org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:769)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1726)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:833)
I dumped the stack trace in OAuth2AuthenticationFailureHandler class.
Update:
For anyone stumbling upon the same issue. I was not able to solve this issue using the above cookie-based configuration. However, I understood why it was not working.
i) User clicks on the sign-in with google. At this time the OAuth2 state will be saved in the cookie.
ii) User will be redirected to the google login page with the appropriate client id and client secret in the URL which the backend will construct
iii) After the user enters the credentials and the sign-in is successful, google will redirect the browser to the redirect-URI provided by the backend in step 1.
iv) Now spring will match the initial OAuth2 request with the data sent by google.
In our case, it tries to get from the cookie. But for some reason, it won't get the cookie that it stored initially which will result in an [authorization_request_not_found] error.
Also if the client explicitly disables cookies in the browser then the above implementation wont work.
So I changed the above configuration from cookie-based implementation to DB-based implementation. This way I can have any amount of backend servers running behind the proxy since the DB will be shared between all the servers.
WebSecurityConfig.java
#SuppressWarnings("deprecation")
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// other config
#Bean
public DBOAuth2AuthorizationRequestRepository dbAuthorizationRequestRepository() {
return new DBOAuth2AuthorizationRequestRepository();
}
// other config
#Override
protected void configure(HttpSecurity http) throws Exception {
// other config
http.authorizationRequestRepository(dbAuthorizationRequestRepository())
// other cofig
}
}
DBOAuth2AuthorizationRequestRepository.java
public class DBOAuth2AuthorizationRequestRepository implements
AuthorizationRequestRepository<OAuth2AuthorizationRequest> {
#Autowired
public OauthStateRepository oauthStateRepository;
#Override
public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest httpServletRequest) {
String state = httpServletRequest.getParameter("state");
OauthState oauthState = oauthStateRepository.findOauthStateByState(state);
return deserialize(oauthState.getSerializedValue(), OAuth2AuthorizationRequest.class);
}
#Override
public void saveAuthorizationRequest(OAuth2AuthorizationRequest oAuth2AuthorizationRequest, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
String id = Generators.timeBasedGenerator().generate().toString();
BigInteger timeStamp = BigInteger.valueOf(System.currentTimeMillis());
oauthStateRepository.saveState(id, httpServletRequest.getSession().getId(), serialize(oAuth2AuthorizationRequest), httpServletRequest.getParameter("redirect_uri"), oAuth2AuthorizationRequest.getState() ,timeStamp);
}
#Override
public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest httpServletRequest) {
return this.loadAuthorizationRequest(httpServletRequest);
}
public static String serialize(Object object) {
return Base64.getUrlEncoder()
.encodeToString(SerializationUtils.serialize(object));
}
public static <T> T deserialize(String value, Class<T> cls) {
return cls.cast(SerializationUtils.deserialize(
Base64.getUrlDecoder().decode(value)));
}
}
OauthState.java
#Entity
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class OauthState {
#Id
private String id;
private String sessionId;
#Column(columnDefinition = "TEXT")
private String serializedValue;
private String redirectUri;
private String state;
#Column(columnDefinition = "bigint default 0")
private BigInteger timestamp;
}
OauthStateRepository.java
#Repository
public interface OauthStateRepository extends JpaRepository<OauthState, Integer> {
#Query(value = "SELECT id, redirect_uri, serialized_value , session_id , state, timestamp FROM oauth_state WHERE state = ?1", nativeQuery = true)
OauthState findOauthStateByState(String state);
#Modifying
#Transactional
#Query(value = "INSERT INTO oauth_state (id, redirect_uri, serialized_value, session_id, state, timestamp) VALUES (?1,?2,?3,?4,?5,?6)", nativeQuery = true)
void saveState(String id, String redirect_uri, String serialized_value, String session_id, String state ,BigInteger timestamp);
#Modifying
#Transactional
#Query(value = "DELETE FROM oauth_state WHERE state = ?1", nativeQuery = true)
void deleteOauthStateByState(String id);
#Modifying
#Transactional
#Query(value = "DELETE FROM oauth_state WHERE timestamp between 0 and ?1", nativeQuery = true)
void deleteOauthStateByTimeStamp(BigInteger timeStamp);
}
Using the above configuration Spring will be able to successfully store and retrieve the OAuth2 state data whenever it's required. You have to make sure that Invalid state data won't be stored in the table. For that, you can use the scheduler to clean up invalid data from the OAuth2 state table every day or so.

Related

Spring security JWT filter throws 500 and HTML instead of 401 and json

I've been having trouble getting security working correctly, half of the problem was fixed with this
Spring Boot Security wont ignore certain paths that dont need to be secured
Second problem is spring is ignoring the HTTP status code on failure and always throws a 500.
When the JWT token is invalid I want to return a 401 and a json response. I keep getting a 500 and the white label html page.
JwtFilter
class JwtFilter(private val tokenService: TokenService) : GenericFilterBean() {
override fun doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain) {
val request = req as HttpServletRequest
val response = res as HttpServletResponse
val httpRequest = request as HttpServletRequest
val path = httpRequest.servletPath.toString().substring(0, 12)
if (path == "/api/v1/auth") {
chain.doFilter(req, res)
return
} else {
val token = TokenUtil.extractToken(request as HttpServletRequest)
if (token != null && token.isNotEmpty()) {
try {
tokenService.getClaims(token)
} catch (e: SignatureException) {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid JWT Signature")
} catch (e: MalformedJwtException) {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid JWT token")
} catch (e: ExpiredJwtException) {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Expired JWT token")
} catch (e: UnsupportedJwtException) {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Unsupported JWT exception")
} catch (e: IllegalArgumentException) {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Jwt claims string is empty")
}
} else {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing auth token")
}
chain.doFilter(req, res)
}
}
}
In my application class too I also have
#SpringBootApplication(exclude = [ErrorMvcAutoConfiguration::class])
Everywhere else in the application ResponseStatusException throws an error with the correct code and in JSON format, here for example when I throw an exception the response will be HTML like
<!doctype html>
HTTP Status 500 – Internal Server Error
body {
font-family: Tahoma, Arial, sans-serif;
}
h1,
h2,
h3,
b {
color: white;
background-color: #525D76;
}
h1 {
font-size: 22px;
}
h2 {
font-size: 16px;
}
h3 {
font-size: 14px;
}
p {
font-size: 12px;
}
a {
color: black;
}
.line {
height: 1px;
background-color: #525D76;
border: none;
}
</style>
HTTP Status 500 – Internal Server Error
Type Exception Report
Message 401 UNAUTHORIZED "Expired JWT token"
Description The server encountered an unexpected condition that prevented it from fulfilling the request.
Exception
org.springframework.web.server.ResponseStatusException: 401 UNAUTHORIZED "Expired JWT token"
events.slap.app.web.security.JwtFilter.doFilter(JwtFilter.kt:40)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:92)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:92)
org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:77)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358)
org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271)
org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
Note The full stack trace of the root cause is available in the server logs.
Apache Tomcat/9.0.35
Instead of throwing exceptions in the filter, do this
response.sendsetStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
or if you want message as well
StringBuilder sb = new StringBuilder();
sb.append("{ ");
sb.append("\"error\": \"Unauthorized\" ");
sb.append("\"message\": \"Unauthorized\"");<--- your message here
sb.append("\"path\": \"")
.append(request.getRequestURL())
.append("\"");
sb.append("} ");
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write(sb.toString());
return;
This problem has been bothered me around three-day and thanks #Kavithakaran Kanapathippillai 's comment.
Following is the way I did
if (token != null && token.isNotEmpty()) {
String msg = new String();
try {
tokenService.getClaims(token)
} catch (SignatureException ex) {
msg = "Invalid JWT signature";
} catch (MalformedJwtException ex) {
msg = "Invalid JWT token";
} catch (ExpiredJwtException ex) {
msg = "Expired JWT token";
} catch (UnsupportedJwtException ex) {
msg = "Unsupported JWT token";
} catch (IllegalArgumentException ex) {
msg = "JWT claims string is empty.";
}
if (msg.isNotEmpty()) {
StringBuilder sb = new StringBuilder();
sb.append("{ ");
sb.append("\"error\": \"Unauthorized\",");
sb.append("\"message\": \"Invalid Token.\",");
sb.append("\"path\": \"")
.append(request.getRequestURL())
.append("\"");
sb.append("} ");
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write(sb.toString());
return;
}
chain.doFilter(req, res)
If you are setting up an ServerAuthenticationEntryPoint that is used by SecurityWebFilterChain (comes with #EnableWebFluxSecurity) you can use this
class HttpBasicServerAuthenticationEntryPoint implements ServerAuthenticationEntryPoint
For some reason, I got 500 thrown instead of 401 when I tried to implement my own EntryPoint. It however works as expected and throws 401 now.

What is correct way to make AJAX call in Spring Boot web Thymeleaf application Getting getOutputStream() has already been called for this response

I am working on Spring boot based web application and using Thymeleaf on front end. I have a requirement to make a AJAX call, POST the data and on-success display data returned from the AJAX call.
below is the AJAX call to call the Controller method
var myObject = new Object();
myObject.acctTypeValue = 22;
myObject.amtBalance = "121334";
myObject.custNo="12121";
myObject.userId="2345rest";
$.ajax({
url : '/testDocWeb/forwardToCallingSystem',
type :'POST',
data : myObject,
dataType:'html',
cache: false,
timeout: 600000,
success : function(data) {
alert('Data: '+JSON.stringify(data));
},
error : function(request,error)
{
alert("Error : "+JSON.stringify(request));
}
});
and below is the Spring MVC controller:-
#PostMapping(path="/forwardToCallingSystem")
public #ResponseBody DummuyResponse forwardToCallingSystem(#ModelAttribute DummyInput dummyInput,Model model, HttpServletRequest request) {
log.info("Inside forwardToCallingSystem method." );
DummuyResponse dummyResponse = new DummuyResponse ();
dummyResponse .setDocId("756");
dummyResponse .setReasonCode('0');
dummyResponse .setReturnCode(100);
dummyResponse .setStatusCode(200);
return dummyResponse ;
}
and below is the Model object which I am returning :-
#Data
#JsonIgnoreProperties(ignoreUnknown = true)
public class DummyResponse implements Serializable {
private static final long serialVersionUID = -8191562770215698813L;
private int statusCode;
private char reasonCode;
private int returnCode;
private String docId;
}
I am not sure what is wrong but I am getting the below error on making that AJAX call:-
[ERROR]~2019-06-12-10.32.54.030CDT~~~~~~ o.a.c.c.C.[.[localhost] Exception Processing ErrorPage[errorCode=0, location=/error]
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalStateException: getOutputStream() has already been called for this response
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1013)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:908)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:660)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:712)
at org.apache.catalina.core.ApplicationDispatcher.doInclude(ApplicationDispatcher.java:580)
at org.apache.catalina.core.ApplicationDispatcher.include(ApplicationDispatcher.java:516)
at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:388)
at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:253)
at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:348)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:173)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at net.rakugakibox.spring.boot.logback.access.tomcat.LogbackAccessTomcatValve.invoke(LogbackAccessTomcatValve.java:91)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:834)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1415)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.IllegalStateException: getOutputStream() has already been called for this response
I am getting issue only on making AJAX call form Thymeleaf web page for normal Spring MVC its working fine .Any help will be appreciated
In your ajax request you are using dataType:'html', But from your controller, you are returning back an Object, According to jQuery’s Ajax-Related Methods Description
// The type of data we expect back
dataType : "html"
Update your ajax request to
$.ajax({
url : '/testDocWeb/forwardToCallingSystem',
type :'POST',
data : myObject,
cache: false,
timeout: 600000,
success : function(data) {
alert('Data: ' + JSON.stringify(data));
},
error : function(request,error) {
alert("Error : " + JSON.stringify(request));
}
});
Also, this Post can be helpful.

Apollo Client throwing NPE when ApolloQueryCall.enqueue is called

I am learning GraphQL Client to Server communication using JAVA. I am using Apollo-Client now. facing issue on executing query throug java client.
My GraphQL Schema:
type Query {
bookById(id: ID): Book
}
type Book {
id: ID
name: String
pageCount: Int
author: Author
}
type Author {
id: ID
firstName: String
lastName: String
}
My Query Schema
query BookById($id: ID!) {
bookById(id: $id) {
name
}
}
I have generated Java Client code from Apollo Gradle Plugin.
Below is my client code:
public class Client {
private static final String BASE_URL = "https://localhost:8080/graphql";
private ApolloClient apolloClient;
private AtomicInteger result;
#Test
public void connectServer() throws InterruptedException {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.build();
apolloClient = ApolloClient.builder()
.serverUrl(BASE_URL)
.okHttpClient(okHttpClient)
.build();
BookByIdQuery bookByIdQuery = new BookByIdQuery("book-1");
ApolloQueryCall<BookByIdQuery.Data> query = apolloClient
.query(bookByIdQuery);
System.out.println(bookByIdQuery.queryDocument());
query.enqueue(new CallBackImpl());
Thread.currentThread().join();
}
private class CallBackImpl extends ApolloCall.Callback<BookByIdQuery.Data> {
#Override
public void onResponse(#Nonnull Response<BookByIdQuery.Data> response) {
log.info(response.data().bookById().name());
}
#Override
public void onFailure(#Nonnull ApolloException e) {
log.info("Fail:" + e.getMessage());
}
}
}
When I ran this client program it is throwing:
Exception in thread "OkHttp Dispatcher" java.lang.NullPointerException
at Client$CallBackImpl.onFailure(Client.java:61)
at com.apollographql.apollo.ApolloCall$Callback.onNetworkError(ApolloCall.java:115)
at com.apollographql.apollo.internal.RealApolloCall$1.onFailure(RealApolloCall.java:238)
at com.apollographql.apollo.internal.interceptor.ApolloCacheInterceptor$1$1.onFailure(ApolloCacheInterceptor.java:91)
at com.apollographql.apollo.internal.interceptor.ApolloParseInterceptor$1.onFailure(ApolloParseInterceptor.java:63)
at com.apollographql.apollo.internal.interceptor.ApolloServerInterceptor$1$1.onFailure(ApolloServerInterceptor.java:89)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:148)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Server Side Error:
ava.lang.IllegalArgumentException: Invalid character found in method name. HTTP method names must be tokens
at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:414) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:294) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-9.0.14.jar:9.0.14]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:834) [tomcat-embed-core-9.0.14.jar:9.0.14]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1417) [tomcat-embed-core-9.0.14.jar:9.0.14]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.14.jar:9.0.14]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_121]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_121]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.14.jar:9.0.14]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_121]
From insomnia client the same call is working fine on URL: https://localhost:8080/graphql:
Replaced https to http in BASE_URL.
Server was not able to understand https request.

spring /oauth/token, AuthorizationFailureEvent -> AuthenticationSuccessEvent

Spring Boot 2, OAuth2, and I use InMemoryTokenStore.
I have defined the following setting:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
...
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.authorizeRequests()
.anyRequest().permitAll();
;
}
...
}
To get a token I'm making the following request:
curl Standard:Login#localhost:8080/oauth/token -d grant_type=password -d username=user -d password=passUser
Next, the following event chain occurs:
1) The first event raised is AuthorizationFailureEvent:
principal = anonymousUser
configAttributes = [fullyAuthenticated]
details = org.springframework.security.web.authentication.WebAuthenticationDetails#957e: RemoteIpAddress: 127.0.0.1; SessionId: null
exceptionClass = AccessDeniedException
source = FilterInvocation: URL: /oauth/token?username=user&password=passUser&grant_type=password
authorities = [ROLE_ANONYMOUS]
exceptionMessage = Access is denied
timestamp = 2017-12-01 14:45:34
2) Then AuthenticationSuccessEvent:
details = remoteAddress=127.0.0.1, tokenType=BearertokenValue=<TOKEN>
source = org.springframework.security.oauth2.provider.OAuth2Authentication#eb5a2d91: Principal: {
"id":" user",
"password": "****",
"authorities": ["USER"],
"firstName": "",
"lastName": "",
"accountNonExpired": "true",
"credentialsNonExpired": "true",
"accountNonLocked": "true",
"enabled": "true"
}; Credentials: [PROTECTED]; Authenticated: true; Details: remoteAddress=127.0.0.1, tokenType=BearertokenValue=<TOKEN>; Granted Authorities: "USER"
timestamp = 2017-12-01 14:45:36
3) And finally the server successfully returns a token:
{
"access_token" : "2d37dd06-0f35-441f-a851-96d145836eed",
"token_type" : "bearer",
"expires_in" : 863999,
"scope" : "all"
}
Please help me understand:
What settings should I make so that event AuthorizationFailureEvent does not occur?

apache Shiro Login

I am kind of new to Apache shiro and trying to use authcBasic for securing the webservice.
I need to create a webservice using which I can login by providing username and password which can utilize apache shiro's features.
Any guidance will highly be appreciated
I have created a minimal example application with Spring-Boot (because of the "spring" tag) and Shiro for you, which you can find here on GitHub. The example application is based on the "hello world" RESTful web service with Spring application from the Spring docs. I have added Shiro to it via these changes (GitHub commit):
Add the shiro-spring dependency to pom.xml:
</dependencies>
[...]
<!-- Apache Shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
Copy shiro.ini from the Shiro docs to resources:
# =============================================================================
# Tutorial INI configuration
#
# Usernames/passwords are based on the classic Mel Brooks' film "Spaceballs" :)
# =============================================================================
# -----------------------------------------------------------------------------
# Users and their (optional) assigned roles
# username = password, role1, role2, ..., roleN
# -----------------------------------------------------------------------------
[users]
root = secret, admin
guest = guest, guest
presidentskroob = 12345, president
darkhelmet = ludicrousspeed, darklord, schwartz
lonestarr = vespa, goodguy, schwartz
# -----------------------------------------------------------------------------
# Roles with assigned permissions
# roleName = perm1, perm2, ..., permN
# -----------------------------------------------------------------------------
[roles]
admin = *
schwartz = lightsaber:*
goodguy = winnebago:drive:eagle5
Configure ShiroFilter, SecurityManager with IniRealm, and Shiro annotations in Application.java (adapted from here):
#SpringBootApplication
public class Application {
[...]
#Bean(name = "shiroFilter")
public FilterRegistrationBean shiroFilter() throws Exception {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter((AbstractShiroFilter) getShiroFilterFactoryBean().getObject());
registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
return registration;
}
#Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager());
Map<String, String> filterChainDefinitionMap = shiroFilterFactoryBean.getFilterChainDefinitionMap();
filterChainDefinitionMap.put("/**", "authcBasic");
return shiroFilterFactoryBean;
}
#Bean(name = "securityManager")
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
dwsm.setRealm(getShiroIniRealm());
final DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
// disable session cookie
sessionManager.setSessionIdCookieEnabled(false);
dwsm.setSessionManager(sessionManager);
return dwsm;
}
#Bean(name = "shiroIniRealm")
#DependsOn("lifecycleBeanPostProcessor")
public IniRealm getShiroIniRealm() {
return new IniRealm("classpath:shiro.ini");
}
#Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
#Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
daap.setProxyTargetClass(true);
return daap;
}
#Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(securityManager());
return new AuthorizationAttributeSourceAdvisor();
}
}
Add #RequiresRoles annotation with parameter "admin" to GreetingController for testing purposes:
#RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
#RequestMapping("/greeting")
#RequiresRoles(value = {"admin"})
public Greeting greeting(#RequestParam(value="name", defaultValue="World") String name) {
return new Greeting(counter.incrementAndGet(),
String.format(template, name));
}
}
Use the following commands to check out and run the application:
git clone https://github.com/opncow/gs-rest-service.git
cd gs-rest-service/complete/
./mvnw spring-boot:run
Verify that Shiro is working (use HttpRequester or similar plugin to create the following requests):
User "root" (has "admin" role) with password "secret" (Base64 encoded username:password as value of the Authorization header)
GET http://localhost:8080/greeting
Authorization: Basic cm9vdDpzZWNyZXQ=
-- response --
200
Set-Cookie: rememberMe=deleteMe; Path=/; Max-Age=0; Expires=Thu, 11-May-2017 00:29:44 GMT
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 12 May 2017 00:29:44 GMT
{"id":1,"content":"Hello, World!"}
User "guest" with password "guest" (no "admin" role):
GET http://localhost:8080/greeting
Authorization: Basic Z3Vlc3Q6Z3Vlc3Q=
-- response --
500
Set-Cookie: rememberMe=deleteMe; Path=/; Max-Age=0; Expires=Thu, 11-May-2017 00:44:18 GMT rememberMe=deleteMe; Path=/; Max-Age=0; Expires=Thu, 11-May-2017 00:44:18 GMT
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 12 May 2017 00:44:18 GMT
Connection: close
{"timestamp":1494549858572,"status":500,"error":"Internal Server Error","exception":"org.apache.shiro.authz.UnauthorizedException","message":"Subject does not have role [admin]","path":"/greeting"}
As can be seen, in the second request, the user guest is authenticated, however not authorized to use the greeting resource because of lacking the "admin" role (which means that the annotation is working).
This is the most minimal example I could imagine. It uses Shiro's .ini configuration/realm for users, passwords, and roles. For a real world project you will likely have to use a more sophisticated realm implementation such as Shiro's JdbcRealm

Resources