I am working on a spring boot + spring security based application. I have used jdbcAuthentication to validate user. I have also configured custom login form.
After running the application I am able to successfully login and get the API response through browser but when I try to test the API using Postman I only get the HTML login page as response. How do I get the desired API json response?
My configuration file:
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
System.out.println("auth manager called");
auth. jdbcAuthentication() .usersByUsernameQuery(usersQuery)
.authoritiesByUsernameQuery(rolesQuery) .dataSource(dataSource)
.passwordEncoder(noop);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("Http scurity called");
http.httpBasic().
and().
authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/registration").permitAll()
.antMatchers("/admin/**").hasAuthority("ADMIN")
.antMatchers("/db").hasAuthority("DBA")
.antMatchers("/user").hasAuthority("USER").anyRequest()
.authenticated().and().csrf().disable().formLogin()
.loginPage("/login").failureUrl("/login?error=true")
.successHandler(customSuccessHandler)
.usernameParameter("username")
.passwordParameter("password")
.and().logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/").and().exceptionHandling()
.accessDeniedPage("/access-denied");
}
My Controller file:
#RequestMapping(value = { "/", "/login" }, method = RequestMethod.GET)
public ModelAndView login() {
System.out.println("/login called");
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("login");
return modelAndView;
}
#RequestMapping(value = "/admin", method = RequestMethod.GET, produces = { "application/json" })
public UserUniconnect home(HttpServletRequest request, HttpServletResponse response) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String currentUser = null;
if (!(auth instanceof AnonymousAuthenticationToken)) {
currentUser = auth.getName();
}
User user1 = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
user1.getAuthorities();
System.out.println("++++++++++++++++++++++++++++++");
System.out.println(request == null);
Users u = (Users) request.getSession(false).getAttribute("user");
Uniconnect uni = (Uniconnect) request.getSession(false).getAttribute("uniconnect");
UserUniconnect uu = new UserUniconnect();
uu.setUser(u);
uu.setUniconnect(uni);
return uu;
}
I am returning java object as the response which spring boot is able to convert it into json format.
Postman Screenshot
Setting up the Basic Auth parameters in Postman might help:
It is most likely that you need to get your session id from a cookie after logging in manually with your browser and then provide this cookie to Postman just like this:
Getting a cookie from browser differs depending on a browser itself, but Chrome and Firefox both have a Developer utils built in, so that should not be a problem.
Related
I created a registration and login with Spring Boot Security and so far all implementation works fine when I test it with Postman. Right now I want to create also a HTML side of that so user can actually sign up and log in.
There is a problem. I'm getting Full authentication is required to access this resource
In the stack trace there is also a line which points on AcessTokenFilter and that is this:
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
Optional<String> accessToken = parseAccessToken(request);
if(accessToken.isPresent() && jwtHelper.validateAccessToken(accessToken.get())) {
String userId = jwtHelper.getUserIdFromAccessToken(accessToken.get());
User user = userService.findById(userId);
UsernamePasswordAuthenticationToken upat = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
upat.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(upat);
}
} catch (Exception e) {
log.error("cannot set authentication", e);
}
filterChain.doFilter(request, response);
}
I'm getting that error when I try to access to: /api/auth/form
And there is a methods:
#PostMapping("signup")
#Transactional
public ResponseEntity<?> signup(#Valid #RequestBody SignupDTO dto, #ModelAttribute User user, Model model) {
User user1 = new User(dto.getUsername(), dto.getEmail(), passwordEncoder.encode(dto.getPassword()));
model.addAttribute("user", user1);
userRepository.save(user1);
RefreshToken refreshToken = new RefreshToken();
refreshToken.setOwner(user);
refreshTokenRepository.save(refreshToken);
String accessToken = jwtHelper.generateAccessToken(user);
String refreshTokenString = jwtHelper.generateRefreshToken(user, refreshToken);
return ResponseEntity.ok(new TokenDTO(user.getId(), accessToken, refreshTokenString));
}
#GetMapping("/form")
public String showForm(Model model) {
model.addAttribute("user", new User());
return "signup";
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(accessTokenEntryPoint).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests().antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(accessTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
And now I saw that on chrome im getting this:
There was an unexpected error (type=Method Not Allowed, status=405).
Request method 'GET' not supported
org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported
As far as i understand you want to go to /api/auth/form and you get the error?
I only see this rest mapping here:
#GetMapping("/form")
is the controller annotated with:
#RequestMapping("/api/auth")
?
Another thing: Try to work in reverse to see which part is preventing this from working: Remove spring security and see if you see the website first. Then turn security on again and try to see which lines are exactly preventing you from accessing the resource
I am trying to add Facebook authorization using Spring Security in Spring Boot app. Currently, my problem is extracting data from Principal.
Here is my security config:
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure (HttpSecurity http) throws Exception {
http
.csrf().disable()
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/", "/login**").permitAll()
.anyRequest().authenticated()
.and()
.logout()
.deleteCookies("JSESSIONID")
.clearAuthentication(true)
.logoutSuccessUrl("/").permitAll();
}
#Bean
public PrincipalExtractor facebookPrincipalExtractor(){
return new FacebookPrincipalExtractor();
}
}
and principal extractor:
public class FacebookPrincipalExtractor implements PrincipalExtractor {
#Autowired
UserService userService;
#Override
public Object extractPrincipal(Map<String, Object> map) {
String name = (String) map.get("name");
String id = (String) map.get("id");
User user = userService.findOne(id);
if (user == null) {
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
String token = ((OAuth2AuthenticationDetails) authentication.getDetails()).getTokenValue();
user = new User();
FacebookClient facebookClient = new DefaultFacebookClient(token, Version.VERSION_2_10);
JSONObject object = facebookClient.fetchObject("me", JSONObject.class);
// userService.createUser(object);
}
return user;
}
}
After login, the Map<String, Object> map contains only the name and id. Call to securityContext.getAuthentication() returns null.
Moreover, if I create something similar to the endpoint and pass the Principal there as a parameter, then this will work. Example:
#RequestMapping("/user")
public Principal user(Principal principal) {
return principal;
}
The principal will contain all the necessary data.
In this regard, 2 questions:
Why security context does not contain authentication?
Where does the principal come from if it is passed as a parameter to a method?
This is what the debug looks like inside
Although SecurityContextHolder.getContext() is never null the authentication it contains is cleared once a request is completed. What this means is that if you try to access it during a process which goes through the spring web security it will be there. But as soon as the request finishes the following gets logged
SecurityContextHolder now cleared, as request processing completed
and the authentication is set to null. Any attempts to access it directly through the SecurityContext outside of an http request will result in a null.
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
authentication.getPrincipal();
use nested call for getting authentication object and then getPrincipal(); will return current loggedin user details
I need to update my Spring Security configuration to introduce multi-tenant management (where I get URL for each web request and through a configuration file I retrieve the correct schema).
So I add a filter (because with handler the login page doesn't have the correct schema since the handler is called after spring security) to my spring security configuration but now I catch the URL, set the schema but the page still empty and doesn't redirect to login page and also if I write /login no HTML page appears.
This is how I have configured spring security:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
private RoleServices roleServices;
#Autowired
private CustomSuccessHandler customSuccessHandler;
#Autowired
public void configAuthentication(AuthenticationManagerBuilder auth)throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.passwordEncoder(passwordEncoder())
.usersByUsernameQuery("select username,password,enabled from user where username=?")
.authoritiesByUsernameQuery("select u.username, CONCAT('ROLE_' , r.role) from user u inner join role r on u.idRole = r.idRole where lower(u.username) = lower(?)");
}
#Bean
public PasswordEncoder passwordEncoder(){
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
#Override
public void configure(WebSecurity web) throws Exception {
web
//Spring Security ignores request to static resources such as CSS or JS files.
.ignoring()
.antMatchers("/static/**","/users/{\\d+}/password/recover","/users/{\\d+}/token/{\\d+}/password/temporary")
.antMatchers(HttpMethod.PUT,"/users/{\\d+}/token/{\\d+}/password/temporary");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
List<Role> roles=roleServices.getRoles();
//Retrieve array of roles(only string field without id)
String[] rolesArray = new String[roles.size()];
int i=0;
for (Role role:roles){
rolesArray[i++] = role.getRole();
}
http
.authorizeRequests() //Authorize Request Configuration
.anyRequest().hasAnyRole(rolesArray)//.authenticated()
.and()//Login Form configuration for all others
.formLogin()
.loginPage("/login").successHandler(customSuccessHandler)
//important because otherwise it goes in a loop because login page require authentication and authentication require login page
.permitAll()
.and()
.exceptionHandling().accessDeniedPage("/403")
.and()
.logout()
.logoutSuccessUrl("/login?logout")
.deleteCookies("JSESSIONID", "JSESSIONID")
.invalidateHttpSession(true)
.permitAll()
.and()
.sessionManagement().invalidSessionUrl("/login")
.and()
.addFilterAfter(new MultiTenancyInterceptor(), BasicAuthenticationFilter.class);
}
}
I added MultiTenancyInterceptor filter where I set the Tenant
#Component
public class MultiTenancyInterceptor extends OncePerRequestFilter {
#Override
public void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws IOException, ServletException {
String url = request.getRequestURL().toString();
URI uri;
try {
uri = new URI(url);
String domain = uri.getHost();
if(domain!=null){
TenantContext.setCurrentTenant(domain);
}
} catch (URISyntaxException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
but as I write the controller of login page doesn't receive the call:
#Override
#RequestMapping(value = { "/login" }, method = RequestMethod.GET)
public String loginPage(){
return "login";
}
Do you see an error in my configure method? If you need further information I can add the other classes. Thanks
PS: I noticed that doFilter is called twice for each page request
Best way is to implement Filter inteface and do some your url logic and then forward it to next action using filterChain.doFilter(request, response);
Make sure to add this filter in web.xml.
Either way is you can use spring org.springframework.web.servlet.handler.HandlerInterceptorAdapter for pre and post handling for http requests. Spring internally forwards to next controller request method.
Example : https://www.mkyong.com/spring-mvc/spring-mvc-handler-interceptors-example/
After the suggest of dur I add the code
filterChain.doFilter(request, response);
at the end of filter method
I have following code with me
I am trying to achieve ldap Authentication but i think it is not happening.
My Security Configuration
#EnableWebSecurity
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class Config extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().authorizeRequests().antMatchers("/*")
.permitAll().anyRequest().authenticated().and().csrf()
.disable().httpBasic().and().csrf()
.csrfTokenRepository(csrfTokenRepository()).and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.ldapAuthentication()
.userSearchFilter("(uid={0})")
.userSearchBase("dc=intern,dc=xyz,dc=com")
.contextSource()
.url("ldap://192.168.11.11:1234/dc=intern,dc=xyz,dc=com")
.managerDn("username")
.managerPassword("password!")
.and()
.groupSearchFilter("(&(objectClass=user)(sAMAccountName=" + "username" + "))");
}
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);
response.sendRedirect("/notAllowed");
}
}
filterChain.doFilter(request, response);
}
};
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
My Controller
#RequestMapping(value = { "/test" }, method = RequestMethod.GET)
public #ResponseBody String retrieve() {
System.out.println("line 1");
System.out.println("line 2");
return "hello";
}
#RequestMapping(value = { "/notAllowed" }, method = RequestMethod.GET)
public #ResponseBody HttpStatus login() {
return HttpStatus.FORBIDDEN;
}
i am aiming for :
i want to achieve ldap authentication. Username and password will come from browser though i have tried with hardcoded username and password as well.
if user is authentic then filter will check the authorizátion by checking the token .
if this is first request then new token will be generated and sent.
if its not found then it will send the HTTP Status forbidden.
I have following problems :
when i run first time from browser it returns forbidden but it also prints "line 1 and line 2" in console though it do not return hello but forbidden.
are my htpSecurity and ldap Configuration fine?.
from 2nd request it always return hello , i have tried to open new tab ,new request but still it works fine .If i restart server then only it generates token and compare it with cookies token.what if two people are using same system (different times).
how exactly i can test ldap authentication ? i am using POSTMAN as a client .
If some information is missing from my end please let me know .
And i will be thankful for your answers.
First of all, I think your HttpSecurity config is wrong. You want to protect ALL the endpoints. Don't you?
So change it to the following:
http.httpBasic()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.csrf()
.csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
Furthermore, I'm not sure whether your ldap config is right. I think you can reduce it to the following:
auth.ldapAuthentication()
.userSearchFilter("uid={0}")
.contextSource()
.url("ldap://192.168.11.11:1234/dc=intern,dc=xyz,dc=com");
Make sure if your userSearchBase is right. It doesn't have an "ou".
If you don't have any different organizational units, you can simply remove the userSearchBase
To provide better help i need to know the structure of your ldap.
If you want to check your HttpSecurity config you may not use ldap in the first place and use inMemoryAuthentication instead:
auth.inMemoryAuthentication().withUser("user").password("password").authorities("ROLE_USER");
I know this question can be found with different solutions. But I am unable to get it working in my project.
We are sending mails to users which has link to perform some action in the application. When user click on url he should be redirect to login page if he is not logged in and after login should be navigated to the targeted URL.
I am trying to fix using CustomLoginSuccessHandler here is the code.:
public class CustomLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
// public CustomLoginSuccessHandler(String defaultTargetUrl) {
// setDefaultTargetUrl(defaultTargetUrl);
// }
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
HttpSession session = request.getSession(false);
if (session != null) {
String redirectUrl = (String) session.getAttribute("url_prior_login");
if (redirectUrl != null) {
// we do not forget to clean this attribute from session
session.removeAttribute("url_prior_login");
// then we redirect
getRedirectStrategy().sendRedirect(request, response, redirectUrl);
} else {
super.onAuthenticationSuccess(request, response, authentication);
}
} else {
super.onAuthenticationSuccess(request, response, authentication);
}
}
}
Configurations I am using are :
#Bean
public SavedRequestAwareAuthenticationSuccessHandler authenticationSuccessHandler(){
CustomLoginSuccessHandler successHandler = new CustomLoginSuccessHandler();
// SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
// successHandler.setUseReferer(true); getting NULL in the controller every time
// successHandler.setTargetUrlParameter("targetUrl"); this also doesnt work as browser is redirect to /login page and URL parameters are lost
return successHandler;
}
protected void configure(HttpSecurity http) throws Exception {
http
.logout().logoutUrl("/logout").deleteCookies("JSESSIONID").logoutSuccessUrl("/logoutSuccess")
.and()
.authorizeRequests()
.antMatchers("/privacyPolicy", "/faq", "/aboutus", "/termsofuse", "/feedback","/feedbackSubmit", "/contactSsm", "/resources/**", "/userReply", "/userReplySubmit", "/image", "/logoutExternal", "/closeit").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.successHandler(authenticationSuccessHandler)
.loginPage("/login")
.defaultSuccessUrl("/")
.permitAll();
// .and().exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint());
}
Problem using this configuration is, If i request for url say 'http:localhost:8080/showPage' spring security is navigating to 'http:localhost:8080/login' and I am unable to capture anything from original URL. Same problem occurs when I try to use a custom variable targetUrl and using it in the same CustomLoginSuccessHandler.
Please let me know if am taking a wrong approach or something else is missing
Also tried using Custom EntryPoint but unable to redirect using my entrypoint.
#Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint{
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
request.getSession().setAttribute("targetUrl",request.getRequestURL());
redirectStrategy.sendRedirect(request,response,request.getRequestURL().toString());
}
}
Controller :
#RequestMapping(value="/login")
public ModelAndView loginHandler(HttpServletRequest request) {
ModelAndView mav = new ModelAndView();
String targetUrl = request.getParameter("targetUrl");
if(targetUrl!=null){ // targetUrl is always null as spring security is navigating to /login asd parameters are lost
request.getSession().setAttribute("url_prior_login",targetUrl);
}
mav.setViewName("login");
return mav;
}
To login, page is navigated to a different domain. and I pass a redirect URL to that domain after successful login it redirects the page back to the redirecturl
<a href="https://domain/sso/identity/login?channel=abc&ru=${externalUrl.applicationUrl}login" >Sign In</a>
Spring Security already stores the request using a RequestCache the default implementation HttpSessionRequestCache stores the last request in the HTTP session. You can access it using the SPRING_SECURITY_SAVED_REQUEST attribute name to get it from the session.
Doing something like this in your controller
public ModelAndView login(HttpServletRequest req, HttpSession session) {
ModelAndView mav = new ModelAndView("login");
if (session != null) {
SavedRequest savedRequest = session.getAttribute("SPRING_SECURITY_SAVED_REQUEST");
if (savedRequest != null) {
mav.addObject("redirectUrl", savedRequest.getRedirectUrl());
}
}
return mav;
}
Then in your JSP you can use the redirectUrl to dynamically construct your URL.
http://your.sso/login?url=${redirectUrl}
The final thing you need to do is to make /login accessible for everyone by adding it to the list which is protected by permitAll(). If you don't do this, you will get into a loop or the last request is overwritten and will always point to the login page.
.antMatchers("/privacyPolicy", "/faq", "/aboutus", "/termsofuse", "/feedback","/feedbackSubmit", "/contactSsm", "/resources/**", "/userReply", "/userReplySubmit", "/image", "/logoutExternal", "/closeit", "/login").permitAll()
You don't need any other custom classes like EntryPoints or AuthenticationSuccessHandler implementations.
However as you are using SSO it would be probably best to investigate a proper integration with the SSO solution instead of this hack with a login page.
You will at least have one problem : HttpSession session = request.getSession();.
getSession()
Returns the current session associated with this request, or if the request does not have a session, creates one.
You should use getSession(false) if you want a null return in case there is no session.
In your case you'll never get a null session.
I had the same issue and have solved it by using SavedRequestAwareAuthenticationSuccessHandler as a successHandler to make Spring handle the saved request that was requested before redirecting to login page when user is not logged.
In WebSecurityConfigurerAdapter:
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String LOGIN_PATH = "/login";
#Autowired
MyApplicationAuthenticationSuccessHandler myApplicationAuthenticationSuccessHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
// Set the default URL when user enters a non internal URL (Like https://my-application.com)
myApplicationAuthenticationSuccessHandler.setDefaultTargetUrl("/myapp/home");
http.authorizeRequests().antMatchers("/resources/**").permitAll().antMatchers(LOGIN_PATH).permitAll().antMatchers("/auto/**").authenticated()
.and().formLogin().loginPage(LOGIN_PATH).permitAll()
.successHandler(myApplicationAuthenticationSuccessHandler).and().logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl(LOGIN_PATH)
.invalidateHttpSession(true).deleteCookies("JSESSIONID").permitAll().and().sessionManagement().invalidSessionUrl(LOGIN_PATH);
}
}
In custom SavedRequestAwareAuthenticationSuccessHandler:
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
#Component("myApplicationAuthenticationSuccessHandler")
public class MyApplicationAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException {
try {
super.onAuthenticationSuccess(request, response, authentication);
} catch (ServletException e) {
// redirect to default page (home in my case) in case of any possible problem (best solution in my case)
redirectStrategy.sendRedirect(request, response, "/myapp/home");
}
}
}