Using AngularJS with SpringSecurity3.2 for CSRF - angularjs

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.

Related

How do I implement Github OAuth2 login with Spring Boot using only REST api?

I am trying to implement Signup with Github in a sample project, where front-end code is made up of ReactJS and Backend API is made up of Spring Boot.
I am not using Server Template Engines such as ThymeLeaf or Mustache, but only creating RESTful APIs.
Below is my Spring Security Configuration.
#RequiredArgsConstructor
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final GithubOAuth2UserService githubOAuth2UserService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/v1/health-check")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/")
.and()
.oauth2Login()
.userInfoEndpoint()
.userService(githubOAuth2UserService);
}
}
Below is my code for GithubOAuth2UserService
#RequiredArgsConstructor
#Service
public class GithubOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private final UserRepository userRepository;
#Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
OAuth2User oAuth2User = delegate.loadUser(userRequest);
String registrationId = userRequest.getClientRegistration().getRegistrationId();
System.out.println(registrationId);
String userNameAttributeName = userRequest.getClientRegistration()
.getProviderDetails().getUserInfoEndpoint()
.getUserNameAttributeName();
System.out.println(userNameAttributeName);
OAuthAttributes attributes = OAuthAttributes.ofGithub(userNameAttributeName, oAuth2User.getAttributes());
User user = saveUser(attributes);
return new DefaultOAuth2User(
Collections.singleton(new SimpleGrantedAuthority(user.getRole().name())),
attributes.getAttributes(),
attributes.getNameAttributeKey()
);
}
#Transactional
protected User saveUser(OAuthAttributes attributes) {
System.out.println("SAVE USER!");
if(userRepository.findByEmail(attributes.getEmail()).isPresent()) {
throw new UserEmailConflictException();
} else {
User user = attributes.toEntity();
return userRepository.save(user);
}
}
}
Finally, OAuthAttributes is as below.
#Getter
public class OAuthAttributes {
private Map<String, Object> attributes;
private String nameAttributeKey;
private String name;
private String email;
#Builder
public OAuthAttributes(Map<String, Object> attributes, String nameAttributeKey, String name, String email) {
this.attributes = attributes;
this.nameAttributeKey = nameAttributeKey;
this.name = name;
this.email = email;
}
public static OAuthAttributes ofGithub(String userNameAttributeName, Map<String, Object> attributes) {
return OAuthAttributes.builder()
.name((String) attributes.get("name"))
.email((String) attributes.get("email"))
.attributes(attributes)
.nameAttributeKey(userNameAttributeName)
.build();
}
public User toEntity() {
return User.builder()
.name(name)
.email(email)
.build();
}
}
Via PostMan, I can see the API result as below, but my ReactJS code doesn't show anything, and just refreshes the page.
On ReactJS, it calls this API like below.
// OnClick function
const result = await axios.get("http://localhost:8080/oauth2/authorization/github");
console.log(result);
UPDATE
I am getting CORS policy violation, and I configured this using a configuration class below. Is this related to SecurityConfig class ?
#Configuration
#EnableWebMvc
public class CORSConfig implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*").allowedMethods("*");
}
}
Thank you so much in advance. If any code needs to be shown, I will happily edit this post.

HttpServletRequest is null when using filter for JWT, headers work on postman but not localhost

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();
:)

Spring Oauth2 Angularjs Login Not Woking

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.

Spring Oauth2 + User Registration

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.

Spring Security: Still can't disable redirect with logoutSuccessHandler

I am using Spring Security with Oauth2 to secure RESTful API. My WebSecurityConfig class looks like this:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private RestAuthenticationSuccessHandler authenticationSuccessHandler;
#Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(
SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/logout").permitAll()
.antMatchers("/ristore/**").authenticated()
.anyRequest().authenticated()
.and()
.formLogin()
.successHandler(authenticationSuccessHandler)
.failureHandler(new SimpleUrlAuthenticationFailureHandler());
http.logout().permitAll();
http.logout().logoutSuccessHandler((new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK)));
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public RestAuthenticationSuccessHandler mySuccessHandler(){
return new RestAuthenticationSuccessHandler();
}
#Bean
public SimpleUrlAuthenticationFailureHandler myFailureHandler(){
return new SimpleUrlAuthenticationFailureHandler();
}
#Configuration
protected static class AuthenticationConfiguration extends
GlobalAuthenticationConfigurerAdapter {
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource("ldap://ldap.mdanderson.edu:389/dc=mdanderson,dc=edu");
contextSource.setUserDn("cn=ris_flow,ou=service accounts,ou=institution,ou=service accounts,dc=mdanderson,dc=edu");
contextSource.setPassword("!BMpl#tform2O15");
contextSource.setReferral("follow");
contextSource.afterPropertiesSet();
LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder> ldapAuthenticationProviderConfigurer = auth.ldapAuthentication();
ldapAuthenticationProviderConfigurer
.userDnPatterns("cn={0},ou=institution,ou=people")
.userSearchBase("")
.contextSource(contextSource);
}
}
}
In order to disable redirect for logout, I added the logoutSuceessHandler.
At the front end, I wrote the following function to handle logout event with AngularJS:
$scope.logout = function() {
$http.post(SERVER + '/logout', {}).success(function() {
$rootScope.authenticated = false;
$window.localStorage.removeItem("access_token");
$location.path("/");
}).error(function(data) {
console.log("Logout failed")
$rootScope.authenticated = false;
});
};
However, I still got the following error at logout:
XMLHttpRequest cannot load http://localhost:8080/logout. The request was redirected to 'http://localhost:8080/login?logout', which is disallowed for cross-origin requests that require preflight.
I tried every solution in the following similar posts and nothing seems to stop the redirect. spring security /logout not working cross origin requests and Spring security - Disable logout redirect.
What am I missing?
EDIT
After I turned on debug mode in logging spring security, here is the output for logout request. Why is it trying match /logout request to /oauth/token?

Resources