Manually authenticate use spring security - java

I am using spring security and it works fine, but now I want to start the security process manually, do to client changes I need to get in my controller the user name and password (the form wont call "j_spring_security_check" directly)
I thought of 2 options with both I have some problems:
After I get the parameters and do something I will send a post request to j_spring_security_check url. My code:
public void test(loginDTO loginDTO) {
MultiValueMap<String, String> body = new LinkedMultiValueMap<String, String>();
HttpHeaders headers = new HttpHeaders();
body.add(
"j_username",
loginDTO.getJ_username());
body.add(
"j_password",
loginDTO.getJ_password());
HttpEntity<?> httpEntity = new HttpEntity<Object>(
body, headers);
headers.add(
"Accept",
MediaType.APPLICATION_JSON_VALUE);
restTemplate.exchange(
"http://localhost:8080/XXX/j_spring_security_check",
HttpMethod.POST,
httpEntity,
HttpServletResponse.class);
}
This doesn't work and I get :500 internal server error why?
second option- I did the following:
public void test2(loginDTO loginDTO, HttpServletRequest request) {
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(
loginDTO.getJ_username(),
loginDTO.getJ_password());
token.setDetails(new WebAuthenticationDetails(request));
Authentication authentication = this.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
this.sessionRegistry.registerNewSession(
request.getSession().getId(),
authentication.getPrincipal());
}
The problem is that onAuthenticationSuccess is not called. and it feels wrong, that I'm missing the point of using spring security.
What is the correct why?

I typically do the following:
#Controller
public class AuthenticationController
{
#Autowired
AuthenticationManager authenticationManager;
#Autowired
SecurityContextRepository securityContextRepository;
#RequestMapping(method = Array(RequestMethod.POST), value = Array("/authenticate"))
public String authenticate(#RequestParam String username, #RequestParam String password, HttpServletRequest request, HttpServletResponse response)
{
Authentication result = this.authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
SecurityContextHolder.getContext.setAuthentication(result);
this.securityContextRepository.saveContext(SecurityContextHolder.getContext(), request, response);
return "successView";
}
}
The reasons for using this approach is:
Very simple, just a few lines of code if you ignore exception handling and such.
Leverages existing Spring Security components.
Uses Spring Security components configured in the application configuration and allows them to be changed as and when required. For example, the authentication may be done against an RDBMS, LDAP, web service, Active Directory, etc. without the custom code needing to worry about it.

When you want to use as most as possible from the normal Authentication Process, then you could create a mocked HttpServletRequest and HttpServletResponse (org.springframework.mock.web.MockHttpServletRequest and org.springframework.mock.web.MockHttpServletResponse) containing login and password, and then invoke
UsernamePasswordAuthenticationFilter.attemptAuthentication(
HttpServletRequest request,
HttpServletResponse response)`
afterwards you will also need to invoke SessionAuthenticationStrategy.onAuthentication(..) and successfulAuthentication(..)
This is all a bit tricky, because of private fileds, so this is my solution:
public class ExtendedUsernamePasswordAuthenticationFilter
extends UsernamePasswordAuthenticationFilter {
#Override
public void manualAuthentication(String login,
String password,
HttpServletRequest httpServletRequest)
throws IOException, ServletException {
/** I do not mock the request, I use the existing request and
manipulate them*/
AddableHttpRequest addableHttpRequest =
new AddableHttpRequest(httpServletRequest);
addableHttpRequest.addParameter("j_username", login);
addableHttpRequest.addParameter("j_password", password);
MockHttpServletResponse mockServletResponse =
new MockHttpServletResponse();
Authentication authentication = this.attemptAuthentication(
addableHttpRequest,
mockServletResponse);
this.reflectSessionStrategy().onAuthentication(
authentication,
addableHttpRequest,
mockServletResponse);
this.successfulAuthentication(addableHttpRequest,
mockServletResponse,
authentication);
}
private SessionAuthenticationStrategy reflectSessionStrategy() {
Field sessionStrategyField =
ReflectionUtils.findField(
AbstractAuthenticationProcessingFilter.class,
"sessionStrategy",
SessionAuthenticationStrategy.class);
ReflectionUtils.makeAccessible(sessionStrategyField);
return (SessionAuthenticationStrategy)
ReflectionUtils.getField(sessionStrategyField, this);
}
}
AddableHttpRequest is like a mock that is based on an real request
public class AddableHttpRequest extends HttpServletRequestWrapper {
/** The params. */
private HashMap<String, String> params = new HashMap<String, String>();
public AddableHttpRequest(HttpServletRequest request) {
super(request);
}
#Override
public String getMethod() {
return "POST";
}
#Override
public String getParameter(final String name) {
// if we added one, return that one
if (params.get(name) != null) {
return params.get(name);
}
// otherwise return what's in the original request
return super.getParameter(name);
}
public void addParameter(String name, String value) {
params.put(name, value);
}
}
An other way, would be implementing your own, authentication filter. Thats a class that invoke the AuthenticationManager.authenticate(Authentication authentication). But this class is also responsible for invoking all the stuff around authentication (what AbstractAuthenticationProcessingFilter.doFilter does)`

OK so I combined #Ralph and #manish answers and this is what I did:
(twoFactorAuthenticationFilter is an extension of UsernamePasswordAuthenticationFilter)
public void manualAuthentication(loginDTO loginDTO, HttpServletRequest request, HttpServletResponse response) throws IOException,
ServletException {
AddableHttpRequest addableHttpRequest = new AddableHttpRequest(
request);
addableHttpRequest.addParameter(
"j_username",
loginDTO.getJ_username());
addableHttpRequest.addParameter(
"j_password",
loginDTO.getJ_password());
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) twoFactorAuthenticationFilter.attemptAuthentication(
addableHttpRequest,
response);
if (token.isAuthenticated()) {
twoFactorAuthenticationFilter.successfulAuthentication(
addableHttpRequest,
response,
null,
token);
}
}
It works fine

Related

Authentication is required to access this resource error

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

Java Spring Security. Logged user information

I want to improve my REST API todo application. I want to add to security configuration, when someone logs in I want to redirect him to endpoint with his generated by Utils userId. I want to achive something like that:
.formLogin().defaultSuccessUrl("/users/(logged in our session userId)").permitAll()
you can do it by adding this few things :
in your configure method in WebSecurityConfigurerAdapter add this line :
.formLogin().successHandler(mySuccessHandler())...
add a bean definition by adding
#Bean
public AuthenticationSuccessHandler mySuccessHandler(){
return new MyCustomAuthenticationSuccessHandler();
}
next you need to create the MyCustomAuthenticationSuccessHandler that implements AuthenticationSuccessHandler.
public class MyCustomAuthenticationSuccessHandler
implements AuthenticationSuccessHandler {
protected Log logger = LogFactory.getLog(this.getClass());
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException {
handle(request, response, authentication);
}
protected void handle(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication
) throws IOException {
String targetUrl = determineYourTargetUrl(request);
if (response.isCommitted()) {
logger.debug(
"Response has already been committed. Unable to redirect to "
+ targetUrl);
return;
}
redirectStrategy.sendRedirect(request, response, targetUrl);
}
protected String determineYourTargetUrl(HttpServletRequest request) {
return "users/" + request.getSession().getId();
}
}
Hope that will help you.

Spring MVC: How to modify #Pathvariable(URI) in Interceptor before going to controller?

I was get Controller's #PathVariable in Pre-Handler Interceptor.
Map<String, String> pathVariable = (Map<String, String>) request.getAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE );
But I wish to modify #PathVariable value(below).
#RequestMapping(value = "{uuid}/attributes", method = RequestMethod.POST)
public ResponseEntity<?> addAttribute(#PathVariable("uuid") String uuid, HttpServletRequest request, HttpServletResponse response) {
//LOGIC
}
How to modify #PathVariable("uuid") value in interceptor before going to controller??
I'm using Spring 4.1 and JDK 1.6. I can't upgrade its.
A general use for interceptors is to apply generic functionality to controllers. I.e. default data shown on all pages, security etc. You want to use it for a single piece of functionality which you generallly shouldn't do.
What are you trying to achieve isn't possible with an interceptor. As first the method to execute is detected based on the mapping data. Before executing the method the interceptor is executed. In this you basically want to change the incoming request and to execute a different method. But the method is already selected, hence it won't work.
As you eventually want to call the same method simply add another request handling method which either eventually calls addAttribute or simply redirects to the URL with the UUID.
#RequestMapping("<your-url>")
public ResponseEntity<?> addAttributeAlternate(#RequestParam("secret") String secret, HttpServletRequest request, HttpServletResponse response) {
String uuid = // determine UUID based on request
return this.addAttribute(uuid,request,response);
}
Try below given code.
public class UrlOverriderInterceptor implements ClientHttpRequestInterceptor {
private final String urlBase;
public UrlOverriderInterceptor(String urlBase) {
this.urlBase = urlBase;
}
private static Logger LOGGER = AppLoggerFactory.getLogger(UrlOverriderInterceptor.class);
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
URI uri = request.getURI();
LOGGER.warn("overriding {0}", uri);
return execution.execute(new MyHttpRequestWrapper(request), body);
}
private class MyHttpRequestWrapper extends HttpRequestWrapper {
public MyHttpRequestWrapper(HttpRequest request) {
super(request);
}
#Override
public URI getURI() {
try {
return new URI(UrlUtils.composeUrl(urlBase, super.getURI().toString())); //change accordingly
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}
}

Spring custom authentication filter and provider not invoking controller method

I'm trying to implement a custom authentication logic with latest version of Spring Boot, Web and Security, but I'm struggling with some issues. I was trying out many solutions in similar questions/tutorials without success or understanding what actually happens.
I'm creating a REST application with stateless authentication, i.e. there is a REST endpoint (/web/auth/login) that expects username and password and returns a string token, which is then used in all the other REST endpoints (/api/**) to identify the user. I need to implement a custom solution as authentication will become more complex in the future and I would like to understand the basics of Spring Security.
To achieve the token authentication, I'm creating a customized filter and provider:
The filter:
public class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public TokenAuthenticationFilter() {
super(new AntPathRequestMatcher("/api/**", "GET"));
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
String token = request.getParameter("token");
if (token == null || token.length() == 0) {
throw new BadCredentialsException("Missing token");
}
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(token, null);
return getAuthenticationManager().authenticate(authenticationToken);
}
}
The provider:
#Component
public class TokenAuthenticationProvider implements AuthenticationProvider {
#Autowired
private AuthenticationTokenManager tokenManager;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String token = (String)authentication.getPrincipal();
return tokenManager.getAuthenticationByToken(token);
}
#Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.equals(authentication);
}
}
The config:
#EnableWebSecurity
#Order(1)
public class TokenAuthenticationSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private TokenAuthenticationProvider authProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**")
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().addFilterBefore(authenticationFilter(), BasicAuthenticationFilter.class);
}
#Bean
public TokenAuthenticationFilter authenticationFilter() throws Exception {
TokenAuthenticationFilter tokenProcessingFilter = new TokenAuthenticationFilter();
tokenProcessingFilter.setAuthenticationManager(authenticationManager());
return tokenProcessingFilter;
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
}
The AuthenticationTokenManager used in the provider (and also in the login process):
#Component
public class AuthenticationTokenManager {
private Map<String, AuthenticationToken> tokens;
public AuthenticationTokenManager() {
tokens = new HashMap<>();
}
private String generateToken(AuthenticationToken authentication) {
return UUID.randomUUID().toString();
}
public String addAuthentication(AuthenticationToken authentication) {
String token = generateToken(authentication);
tokens.put(token, authentication);
return token;
}
public AuthenticationToken getAuthenticationByToken(String token) {
return tokens.get(token);
}
}
What happens:
I'm appending a valid token in the request to "/api/bla" (which is a REST controller returning some Json). The filter and provider both get invoked. The problem is, the browser is redirected to "/" instead of invoking the REST controller's requested method. This seems to happen in SavedRequestAwareAuthenticationSuccessHandler, but why is this handler being used?
I tried
to implement an empty success handler, resulting in a 200 status code and still not invoking the controller
to do authentication in a simple GenericFilterBean and setting the authentication object via SecurityContextHolder.getContext().setAuthentication(authentication) which results in a "Bad credentials" error page.
I would like to understand why my controller is not being called after I authenticated the token. Besides that, is there a "Spring" way to store the token instead of storing it in a Map, like a custom implementation of SecurityContextRepository?
I really appreciate any hint!
Might be a little late but I was having the same problem and adding:
#Override
protected void successfulAuthentication(
final HttpServletRequest request, final HttpServletResponse response,
final FilterChain chain, final Authentication authResult)
throws IOException, ServletException {
chain.doFilter(request, response);
}
to my AbstractAuthenticationProcessingFilter implementation did the trick.
Use setContinueChainBeforeSuccessfulAuthentication(true) in constructor

Return empty JSON from spring controller for void response

I'm using Java 8, Tomcat 8, Spring-WebMVC 4.2.2.RELEASE, FasterXML 2.6.3.
I have the following method in my controller
#RequestMapping(method = RequestMethod.POST)
#ResponseBody
public void updateCurrentUserDetails(#RequestBody final UserDTO userDTO) {
final UserWithId user = SecurityUtil.getCurrentUser();
this.userAccountService.updateUserDetails(user.getUserId(), user.getUsername(), userDTO);
}
This method returns void which resolves in an empty (0 byte) response. However the clients connecting to the server always expect JSON reponses even, if its an empty response.
So I would like to configure Spring/Jackson to return {} (2 byte) in that case.
I already thought about returning new Object() everywhere in the calls that would return void otherwise but IMO this is a dirty soution and there must be something better.
There shouldn't be any need to do all that. You can just use a 204 response code, which is made for the situation you are describing. You don't even need the ResponseBody annotation, just use:
#RequestMapping(method = RequestMethod.POST)
#ResponseStatus(HttpStatus.NO_CONTENT)
public void updateCurrentUserDetails(#RequestBody final UserDTO userDTO) {
final UserWithId user = SecurityUtil.getCurrentUser();
this.userAccountService.updateUserDetails(user.getUserId(), user.getUsername(), userDTO);
}
204 response code:
The 204 (No Content) status code indicates that the server has
successfully fulfilled the request and that there is no additional
content to send in the response payload body.
Its quite easy.
Just add the following to your spring xml/java config
<mvc:interceptors>
<bean class="de.st_ddt.util.VoidResponseHandlerInterceptor" />
</mvc:interceptors>
And add this class to your classpath
public class VoidResponseHandlerInterceptor extends HandlerInterceptorAdapter {
private static final String voidResponse = "{}";
#Override
public void postHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler,
final ModelAndView modelAndView) throws IOException {
// Returned void?
if (!response.isCommitted()) {
// Used ModelAndView?
if (modelAndView != null) {
return;
}
// Access static resource?
if (DefaultServletHttpRequestHandler.class == handler.getClass()) {
return;
}
response.setStatus(200);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
try (final Writer writer = response.getWriter()) {
writer.write(voidResponse);
}
response.flushBuffer();
}
}
}

Categories

Resources