Currently I'm developing Spring OAuth2 security project with Angularjs. I'm taking a token with oauth server and I'm parsing to headers but when I try to redirect to home page I'm thrown by "Full authentication is required to access this resource" but I loged in and client server gives an anonymousUser and access denied.
#Configuration
#EnableWebSecurity
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/login.html")
.antMatchers("/js/**")
.antMatchers("/css/**")
.antMatchers("/metronic/css/**")
.antMatchers("/metronic/js/**")
.antMatchers("/metronic/image/**")
.antMatchers("/image/**")
.antMatchers("/language/**")
.antMatchers("/404.html")
.antMatchers("/logout")
.antMatchers("/kilitEkrani.html")
.antMatchers("/metronic/css/fonts/**")
.antMatchers("/metronic/fonts/**");
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/css/**", "/metronic/css/**").permitAll()
.and().authorizeRequests().antMatchers("/metronic/image/**", "/image/**", "/metronic/css/fonts/**", "/metronic/fonts/**").permitAll()
.and().authorizeRequests().antMatchers("/js/**", "/metronic/js/**").permitAll()
.and().httpBasic().and().authorizeRequests()
.antMatchers("/login.html", "/language/**", "/api/kullanici/user", "/logout", "/kilitEkrani.html", "/404.html").permitAll()
.anyRequest().authenticated().and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class).csrf().csrfTokenRepository(csrfTokenRepository()).and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login.html")
.permitAll().and().csrf().disable();
}
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class
.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null
&& !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
This is my security config. Am I missing something? Help please...
I think the problem is that you make use of basic authentication in the auth server. You can try to disable the basic authentication and use form authentication instead.
Related
I am using React for front-end and Java spring boot for backend. My api was working before I used Bcrypt to encode passwords but now there seems to be a problem with the internal filter before every api call where the response is null...
this is my WebSecurityConfig.java
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService myUserDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncode());
}
#Bean
public PasswordEncoder passwordEncode(){
return new BCryptPasswordEncoder();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
.authorizeRequests().antMatchers("/authenticate").permitAll()
.antMatchers("/personInfo").permitAll()
.antMatchers("/signup").permitAll().
anyRequest().authenticated().and().
addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class).exceptionHandling();
}
}
and this is my JWTRequestFilter.java
I was thinking it may have to do with the passwordEncoder() because my calls were working when i didn't use BcryptPasswordEncoder()...
#Component
public class JwtRequestFilter extends OncePerRequestFilter {
#Autowired
private MyUserDetailsService userDetailsService;
#Autowired
private JwtUtil jwtUtil;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
if(request == null){
System.out.println("request is null");
}
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
i am using react for the front-end and am calling with axios
async totals(){
console.log('Bearer ', localStorage.getItem('id_token'));
let data = await axios.get("http://localhost:8080/totals", {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('id_token')
}
})
.then(this._checkStatus);
return data.request.response;
}
the api works when i use the token in postman and so the problem is between the initial request and the filter...
the error I get is -
Access to XMLHttpRequest at 'http://localhost:8080/totals' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
thanks for your time :)
If anybody ever gets the same problem, I solved it by adding this line at the end of my configure(HttpSecurity httpSecurity) method in WebSecurityConfig.java
httpSecurity.cors();
:)
I have problem with Spring Oauth2 again. I know this topic is not easy to suggest sth or check the codes because we have too much configuration.
My project has 3 different servers, Authentication server, resource server and front-end server. I want to put register.html to user's registration in front-end project(under Angularjs files) but when I make request to the related url (http://localhost:7080/app/#register) its redirecting to the login page (http://localhost:9080/auth-service/login) only for a second i can see my register.html content but after that its going to login page.
The question is, where should i put this register.html, it should be under front-end project or authentication server ?
My authentication server and front-end server codes are;
#Configuration
public class AuthServerSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.parentAuthenticationManager(authenticationManager);
auth.authenticationProvider(userAuthProviderService());
}
private CsrfMatcher csrfRequestMatcher = new CsrfMatcher();
#Override
protected void configure(final HttpSecurity http) throws Exception {
/*http.csrf().disable();*/
http.csrf().requireCsrfProtectionMatcher(csrfRequestMatcher);
http
.formLogin().loginPage("/login").defaultSuccessUrl("/")
/*.failureUrl("")*/.successHandler(new AuthSuccessHandler()).permitAll()
.and()
.requestMatchers().antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access","/register")
.and()
.authorizeRequests().anyRequest().authenticated();
}
#Bean
public UserAuthProviderService userAuthProviderService(){
return new UserAuthProviderService();
}
private class CsrfMatcher implements RequestMatcher {
#Override
public boolean matches(HttpServletRequest request) {
return false;
}
}
}
#Configuration
#EnableAutoConfiguration
#RestController
#EnableZuulProxy
#EnableOAuth2Sso
#EnableOAuth2Client
public class UIServiceMain {
public static void main(String[] args) {
SpringApplication.run(UIServiceMain.class, args);
}
#Configuration
protected static class SecurityConfiguration extends OAuth2SsoConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.logout().and()
.antMatcher("/**").authorizeRequests()
.antMatchers("/index.html", "/home.html", "/", "/login","/register.html").permitAll().anyRequest()
.authenticated().and().csrf().disable();
http.headers().frameOptions().disable(); //FOR EMBED MAP
}
//unused
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class
.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null
&& !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
//unused
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
}
in your UI server try to create websecurity with /register.hml enabled, something like this
#Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/register.html")
.and()
.authorizeRequests()
.anyRequest().authenticated();
}
}
edit:
or maybe in your current configuration remove .antMatcher("/**").authorizeRequests() and add and() .authorizeRequests().anyRequest().authenticated()
so finally it could be something like this:
#Override
public void configure(HttpSecurity http) throws Exception {
http.logout().and()
.antMatchers("/index.html", "/home.html", "/", "/login","/register.html").permitAll().anyRequest()
.authenticated()
.and().csrf().disable();
http.headers().frameOptions().disable() //FOR EMBED MAP
.and()
.authorizeRequests()
.anyRequest().authenticated();
}
Couple of things:
I can't think of a good reason not to put your *.html anywhere other than front end server.
Also, in general, you should permit access to your static UI components publically, like #bilak mentioned:
.antMatchers("/index.html", "/home.html", "/", "/login","/register.html").permitAll()
If you are able to see register.html page at all (assuming unauthenticated user) then it is public already
Perhaps, there is a webservice call on register.html's load event that is behind Spring security that is triggering the auth flow.
I am using as Server-side Spring-boot and providing a dummy service for test
where my ServiceCaller.java=
package com.user.server.mfw;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.handler.MappedInterceptor;
#RestController
public class ServiceCaller {
#CrossOrigin(allowedHeaders="*",allowCredentials="true")
#RequestMapping(value="/serviceCaller",method=RequestMethod.POST, headers="content-type=text/*")
#ResponseBody
String serviceListener(#RequestParam("serviceName") String serviceName,HttpSession session,HttpServletRequest theHttpServletReq ) throws IOException
{
if(!serviceName.isEmpty())
{
byte[] encoded = Files.readAllBytes(Paths.get("C://Users//something//Desktop//asd.json"));
return new String(encoded, "UTF-8");
}
return "gelemedi";
}
private void checkActiveSessionControl(HttpSession session)
{
System.out.println("Session Id:" + session.getId() +" // " + session.getCreationTime());
if(session == null)
System.out.println("Null");
else if(session.isNew())
System.out.println("New");
else
System.out.println("Old");
}
}
where my client-side is a ionic framework and based on angular.js...
Controller.js
$scope.getInfo = function() {
$http({
url: SERVER_ENDPOINT.url + '/serviceCaller',
method: 'POST',
params: {serviceName: 'deneme'},
withCredentials: true
}).then(function(result) {
var alertPopup = $ionicPopup.alert({
title: 'ALOHA!',
template: 'dksjd ' + result
});
$scope.memberInfo = result.data.accountNumber;
}, function() {
var alertPopup = $ionicPopup.alert({
title: ' failed!',
template: 'da'
});
}
);
};
Basically I get a "invalid HTTP status code 403" when I use POST method instead of GET. However I would like to use POST for calling instead of GET.
However I could not figure out where I am making a mistake....
any solution will be appreciated!
If your browser is sending a pre-flight OPTIONS request , all you have to do is to allow that in your WebSecurity configuration by allowing http OPTIONS.
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
I think you're not passing any parameter using this anotation:
#CrossOrigin(allowedHeaders="*",allowCredentials="true")
#RequestMapping(value="/serviceCaller",method=RequestMethod.POST, headers="content-type=text/*")
#ResponseBody
String serviceListener(#RequestParam("serviceName") String serviceName,HttpSession session,HttpServletRequest theHttpServletReq ) throws IOException
{
you should replace value="/serviceCaller" by value="/{serviceCaller}"
EDIT
please add this class to your project to solve the CORS problems
#Component
public class SimpleCORSFilter implements Filter {
#Override
public void init(FilterConfig fc) throws ServletException {}
#Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
// TODO Auto-generated method stub
HttpServletResponse response = (HttpServletResponse) resp;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
chain.doFilter(req, resp);
}
#Override
public void destroy() {}
}
As shown in the AngularJS docs
XSRF is a technique by which an unauthorized site can gain your user's private data. Angular provides a mechanism to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie (by default, XSRF-TOKEN) and sets it as an HTTP header (X-XSRF-TOKEN). Since only JavaScript that runs on your domain could read the cookie, your server can be assured that the XHR came from JavaScript running on your domain. The header will not be set for cross-domain requests.
so the default header is x-xsrf-token.
Add this filter in your websecurityconfiguration after CsrfFilter
public class CsrfHeaderFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie==null || token!=null && !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
}
add the filter as shown here:
protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()...
.and()
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
}
}
I found the explanation of preflight request very clear in answer to question Angularjs Post not sending headers to Spring JWT.
Since you are using Spring Security, you have to enable CORS at Spring Security level as well to allow it to leverage the configuration defined at Spring MVC level as:
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()...
}
}
Here is very excellent tutorial explaining CORS support in Spring MVC framework.
This enables HttpMethod.Options request to include headers in preflight request.
I am trying to successfully authenticate a user with Spring Security using AngularJS on the front end.
Basically what is supposed to happen is:
1) A new user should be able to fill out the registration form with a unique username and password
2) Upon submit, Angular POSTs the user/pass combination to the URL specified by spring security.
3) Spring Security verifies the account and logs the user in.
4) The user's session begins and is shown as logged in on the front end.
The hangup is occurring in step 2. The information is not successfully posted to the spring login URL. My failure handler is tripped (in the code below) and the login process is halted.
POST http://localhost:8080/libroomreserve/login 401 (Unauthorized)
Here is my Spring Security Config:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
private AuthenticationFailure authFailure;
#Autowired
private AuthenticationSuccess authSuccess;
#Autowired
private EntryPointUnauthorizedHandler unauthorizedHandler;
#Autowired
private UserDetailServiceImpl userDetails;
#Override
protected void configure(HttpSecurity http) throws Exception{
http
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.formLogin()
.successHandler(authSuccess) //sets status to 200 OK
.failureHandler(authFailure) //sets status to 401 Unauthorized
.and()
.authorizeRequests()
.antMatchers("/**")
.permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetails);
}
Failure Handler:
#Component
public class AuthenticationFailure extends SimpleUrlAuthenticationFailureHandler{
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
}
Success Handler:
#Component
public class AuthenticationSuccess extends SimpleUrlAuthenticationSuccessHandler{
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_OK);
}
}
AuthenticationEntryPoint:
#Component
public class EntryPointUnauthorizedHandler implements AuthenticationEntryPoint{
#Override
public void commence(HttpServletRequest hsr, HttpServletResponse hsr1, AuthenticationException ae) throws IOException, ServletException {
hsr1.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Access Denied.");
}
}
On the AngularJS side, this is my service:
.factory('sessionService', function($http, $base64){
var session = {};
session.login = function(data){
return $http.post("/libroomreserve/login", "username=" + data.userName + "&password" + data.password,
{
headers: {
'Content-Type' : 'application/x-www-form-urlencoded'
}
})
//.then() is a "PROMISE" which is executed after initial return function is performed
.then(function(){
console.log("Logged in the user!");
localStorage.setItem("session", {});
}, function(){
console.log("Error logging in the user...");
});
};
session.logout = function(){
localStorage.removeItem("session");
console.log("User has been logged out.");
};
session.isLoggedIn = function(){
return localStorage.getItem("session") !== null;
};
return session;
})
For reference, I am following the tutorial here by Chris Henkel. I can't find any discrepancies between his code and mine.
As an aside, the newly-registered user is being logged into the database so the credentials are available for authentication.
AngularJS
index.html
<head>
<meta name="_csrf" content="${_csrf.token}"/>
<!-- default header name is X-CSRF-TOKEN -->
<meta name="_csrf_header" content="${_csrf.headerName}"/>
</head>
SpringSecurity 3.2
Spring uses HttpSessionCsrfTokenRepository which by default gives header name for CSRF as X-CSRF-TOKEN, however Anuglar convention is X-XSRF-TOKEN
I wanted to extend HttpSessionCsrfTokenRepository and override the header name, but since it is marked final I ended up implementing a custom token repository.
#Component
public class CustomCsrfTokenRepository implements CsrfTokenRepository {
public static final String CSRF_PARAMETER_NAME = "_csrf";
public static final String CSRF_HEADER_NAME = "X-XSRF-TOKEN";
private final Map<String, CsrfToken> tokenRepository = new ConcurrentHashMap<>();
public CustomCsrfTokenRepository() {
log.info("Creating {}", CustomCsrfTokenRepository.class.getSimpleName());
}
#Override
public CsrfToken generateToken(HttpServletRequest request) {
return new DefaultCsrfToken(CSRF_HEADER_NAME, CSRF_PARAMETER_NAME, createNewToken());
}
#Override
public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
String key = getKey(request);
if (key == null)
return;
if (token == null) {
tokenRepository.remove(key);
} else {
tokenRepository.put(key, token);
}
}
#Override
public CsrfToken loadToken(HttpServletRequest request) {
String key = getKey(request);
return key == null ? null : tokenRepository.get(key);
}
private String getKey(HttpServletRequest request) {
return request.getHeader("Authorization");
}
private String createNewToken() {
return UUID.randomUUID().toString();
}
}
SecurityConfig.java
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Inject
private CustomCsrfTokenRepository customCsrfTokenRepository;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// .addFilterAfter(new CsrfTokenGeneratorFilter(), CsrfFilter.class)
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.formLogin()
.loginProcessingUrl("/app/authentication")
.successHandler(ajaxAuthenticationSuccessHandler)
.failureHandler(ajaxAuthenticationFailureHandler)
.usernameParameter("j_username")
.passwordParameter("j_password")
.permitAll()
.and()
.csrf()
.csrfTokenRepository(customCsrfTokenRepository)
.and()
}
}
How can I cleanly override the header name instead of creating a custom csrfTokenRepository?
Is there any other configuration changes I need to do for Single Page
Applications such as AngularJS, as this does not work yet.
When using Java configuration for Spring Security, the following should be possible:
public void configure(final HttpSecurity http) throws Exception
{
final HttpSessionCsrfTokenRepository tokenRepository = new HttpSessionCsrfTokenRepository();
tokenRepository.setHeaderName("X-XSRF-TOKEN");
http.csrf().csrfTokenRepository(tokenRepository);
}
The complication is that single-page applications rely on AJAX and including CSRF tokens with AJAX requests is a bit complicated. When using AngularJS, the server should send a session cookie called XSRF-TOKEN upon first request and whenever a user logs in or logs out. AngularJS will then return the value of this cookie in the HTTP header X-XSRF-TOKEN with all requests, which the server can then check.