I'm developing spring boot app with JWT. When I try permit all to 2 endpoints it isnt working. All api is secured and requires token.
Please help me write configuration. Here is my code:
Security config:
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
#Configuration
public class AdapterJWTSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Override
protected void configure(AuthenticationManagerBuilder auth) {
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.cors()
.and()
.authorizeRequests()
.antMatchers("/user/signIn", "/user/addUser").permitAll()
.and()
.anonymous()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.addFilter(new JWTAuthorizationFilter(authenticationManagerBean()));
}
}
I tried to do this in 2 spring security configurations but it didnt work.
JwtFilter:
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authManager) {
super(authManager);
}
#Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws IOException, ServletException {
String token = req.getHeader(JwtConstants.HEADER_STRING);
if (token == null) {
try {
chain.doFilter(req, res);
} catch (JwtException e) {
System.out.println(e.getMessage());
}
return;
}
UsernamePasswordAuthenticationToken authentication = getAuthentication(req, res);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
String token = request.getHeader(JwtConstants.HEADER_STRING);
if (token != null) {
Claims claims = validateToken(token);
if (claims != null) {
return new UsernamePasswordAuthenticationToken(claims, null, new ArrayList<>());
}
return null;
}
return null;
}
private Claims validateToken(String token) {
Claims claims = null;
try {
return Jwts.parser()
.setSigningKey(JwtConstants.SECRET)
.parseClaimsJws(token).getBody();
} catch (Exception e) {
return null;
}
}
}
Controller
If it could be helpful
#CrossOrigin(origins = "*")
#RestController
#RequestMapping("/rest/user/")
public class UserController {
private UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
#PostMapping("addUser")
public List<String> addUser(#Valid #RequestBody User user,BindingResult bindingResult) {
return userService.addNewUser(user, bindingResult);
}
#PostMapping("signIn")
public List<String> generate(#Valid #RequestBody UserRequestLogin user) {
return userService.signIn(user);
}
}
Related
I have implemented a custom exception handler that handles all the exception that I am throwing:
#ControllerAdvice
public class AppExceptionHandler {
#ExceptionHandler(value = {UserServiceException.class})
public ResponseEntity<Object>handleUserServiceException(
UserServiceException ex, WebRequest request){
ErrorMessage errorMessage = new ErrorMessage(new Date(), ex.getMessage());
return new ResponseEntity<>(errorMessage, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR );
}
//A universal exception handling method
#ExceptionHandler(value = {Exception.class})
public ResponseEntity<Object>handleUserOtherExceptions(Exception ex, WebRequest request){
ErrorMessage errorMessage = new ErrorMessage(new Date(), ex.getMessage());
return new ResponseEntity<>(errorMessage, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR );
}
}
The Universal exception handler method doesn't gets called for 403 status response, the response body is empty but I want to see a response body.
Below is my Security class:
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter{
private final UserService userService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
// #Autowired
// AuthenticationFailureHandler authenticationFailureHandler;
public WebSecurity(UserService userService, BCryptPasswordEncoder bCryptPasswordEncoder) {
this.userService = userService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, SecurityConstants.SIGN_UP_URL)
.permitAll()
.anyRequest()
.authenticated()
.and()
.addFilter(getAuthenticationFilter())
.addFilter(new AuthorizationFilter(authenticationManager()))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService)
.passwordEncoder(bCryptPasswordEncoder);
}
public AuthenticationFilter getAuthenticationFilter() throws Exception{
final AuthenticationFilter filter = new AuthenticationFilter(authenticationManager());
filter.setFilterProcessesUrl("/users/login");
return filter;
}
Below is my AuthenticationFilter:
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter{
private final AuthenticationManager authenticationManager;
public AuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException {
try {
UserLoginRequestModel creds = new ObjectMapper()
.readValue(req.getInputStream(), UserLoginRequestModel.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
creds.getEmail(), creds.getPassword(), new ArrayList<>()));
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest req
, HttpServletResponse res
, FilterChain chain
, Authentication auth)
throws IOException, ServletException {
String userName = ((User) auth.getPrincipal()).getUsername();
String token = Jwts.builder()
.setSubject(userName)
.setExpiration(new Date(System.currentTimeMillis() + SecurityConstants.EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SecurityConstants.getTokenSecret())
.compact();
UserService userService = (UserService) SpringApplicationContext.getBean("userServiceImpl");
UserDto userDto = userService.getUser(userName);
res.addHeader(SecurityConstants.HEADER_STRING, SecurityConstants.TOKEN_PREFIX + token);
res.addHeader(SecurityConstants.USER_ID, userDto.getUserId());
}
}
Below is my AuthorizationFilter:
public class AuthorizationFilter extends BasicAuthenticationFilter {
public AuthorizationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
String header = request.getHeader(SecurityConstants.HEADER_STRING);
if(header == null || !header.startsWith(SecurityConstants.TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader(SecurityConstants.HEADER_STRING);
if(token != null) {
token = token.replace(SecurityConstants.TOKEN_PREFIX, "");
String user = Jwts.parser()
.setSigningKey(SecurityConstants.getTokenSecret())
.parseClaimsJws(token)
.getBody()
.getSubject();
if(user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}
}
return null;
}
}
Below is my postman response (As you can see the body is empty
I want Spring to handle all other exceptions by calling the overriding #ExceptionHandler I have written in the above AppExceptionHandler class.
As you can see the response body is empty. How do I return the default or custom object for response body?
Im not quite sure if this is what you are looking for, but If you want to somehow customize the messages from failed authentication have a look on AuthenticationEntryPoint like in https://www.baeldung.com/spring-security-basic-authentication. There you can react on the thrown exception and customize what message and codes to return.
I have implemented SpringSecurity with JWT, all working nicely except one thing
#PreAuthorize("hasAuthority('admin:read')") isnt working, it gives
403 FORBIDDEN error
I will share parts of important codes. The token return all authorities properly which I added as SimpleGrantedAuthority
SecurityConfig
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...logic
}
AccountController
#RestController
#RequestMapping("/api")
public class AccountController {
//this works
#RequestMapping(value = "/accounts", method = RequestMethod.GET)
#PreAuthorize("hasAnyRole('ROLE_ADMIN')")
public ResponseEntity<?> loadAll() {
List<AccountDTO> res = accountService.loadAll();
return new ResponseEntity(new ResponseWrapper(res), HttpStatus.OK);
}
//this doesn't work
#RequestMapping(value = "/accounts/{uid}", method = RequestMethod.GET)
#PreAuthorize("hasAuthority('admin:read')")
ResponseEntity<?> findByUId(#PathVariable String uid) throws Exception {
AccountDTO dto = this.accountService.findByUid(uid);
return new ResponseEntity<>(new ResponseWrapper(dto), HttpStatus.OK);
}
}
Token
{
"sub": "nenad.arbutina",
"authorities": [
{
"authority": "ROLE_ADMIN"
},
{
"authority": "admin:read"
},
{
"authority": "admin:create"
},
{
"authority": "admin:update"
},
{
"authority": "admin:delete"
}
],
"iat": 1631111042,
"exp": 1632261600
}
update on code. When debugged I am getting only ROLE_ADMIN, so I am confused
SecurityConfig
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final MyUserDetailService myUserDetailService;
private final SecretKey secretKey;
private final JwtConfig jwtConfig;
#Autowired
public SecurityConfig(
MyUserDetailService myUserDetailService,
SecretKey secretKey,
JwtConfig jwtConfig) {
this.myUserDetailService = myUserDetailService;
this.secretKey = secretKey;
this.jwtConfig = jwtConfig;
}
#Bean
PasswordEncoder getEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(new JwtUsernameAndPasswordAuthenticationFilter(authenticationManager(), jwtConfig, secretKey))
.addFilterAfter(new JwtTokenVerifier(secretKey, jwtConfig), JwtUsernameAndPasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/", "index","/css/*", "/js/*").permitAll()
.anyRequest()
.authenticated();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new
DaoAuthenticationProvider();
provider.setPasswordEncoder(getEncoder());
provider.setUserDetailsService(myUserDetailService);
return provider;
}
}
JwtTokenVerifier
public class JwtTokenVerifier extends OncePerRequestFilter {
private final SecretKey secretKey;
private final JwtConfig jwtConfig;
public JwtTokenVerifier(SecretKey secretKey,
JwtConfig jwtConfig) {
this.secretKey = secretKey;
this.jwtConfig = jwtConfig;
}
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String authorizationHeader = request.getHeader(jwtConfig.getAuthorizationHeader());
if (Strings.isNullOrEmpty(authorizationHeader) || !authorizationHeader.startsWith(jwtConfig.getTokenPrefix())) {
filterChain.doFilter(request, response);
return;
}
String token = authorizationHeader.replace(jwtConfig.getTokenPrefix(), "");
try {
Jws<Claims> claimsJws = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token);
Claims body = claimsJws.getBody();
String username = body.getSubject();
List<Map<String, String>> authorities = (List<Map<String, String>>) body.get("authorities");
Set<SimpleGrantedAuthority> simpleGrantedAuthorities = authorities.stream()
.map(m -> new SimpleGrantedAuthority(m.get("authority")))
.collect(Collectors.toSet());
Authentication authentication = new UsernamePasswordAuthenticationToken(
username,
null,
simpleGrantedAuthorities
);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
} catch (JwtException e ){
throw new IllegalStateException(String.format("Token %s is not to be trusted", token));
}
}
}
I created a Spring Boot Rest Api with custom JWT authentication.
My problem is that when I'm sending for example a request with an expired, or invalid JWT token, I'm getting an exception like this:
com.auth0.jwt.exceptions.SignatureVerificationException: The Token's Signature resulted invalid when verified using the Algorithm: HmacSHA512
Which is obvoiusly OK, but the response body is empty and therefore the client has no clue why is the 403 error.
Problem is same with Spring's BadCredentials Exception etc...
How do I convert these exceptions into custom error responses instead of "403 forbidden"?
Spring Web Config:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsServiceImpl userDetailsService;
#Autowired
public WebSecurityConfig(UserDetailsServiceImpl userDetailsService) {
this.userDetailsService = userDetailsService;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
JWTAuthenticationFilter filter = new JWTAuthenticationFilter(authenticationManager());
filter.setFilterProcessesUrl(AUTH_URL);
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
.anyRequest().authenticated()
.and()
.addFilter(filter)
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}
JWTAuthenticationFilter
private final AuthenticationManager authenticationManager;
#Autowired
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req,
HttpServletResponse res) throws AuthenticationException {
try {
String decoded = new String(Base64.getDecoder().decode(new String(req.getInputStream().readAllBytes())));
AuthenticationDetails details = new Gson().fromJson(decoded, AuthenticationDetails.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
details.getUsername(),
details.getPassword(),
new ArrayList<>()));
} catch (TokenExpiredException e) {
req.setAttribute("expired", e.getMessage());
throw new TokenExpiredException(e.getMessage());
} catch (Exception e){
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth) throws IOException, ServletException {
String token = JWT.create()
.withSubject(((User) auth.getPrincipal()).getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.sign(Algorithm.HMAC512(SECRET.getBytes()));
res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
}
}
JWTAuthorizationFilter
public JWTAuthorizationFilter(AuthenticationManager authManager) {
super(authManager);
}
#Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws IOException, ServletException {
String header = req.getHeader(HEADER_STRING);
if (header == null || !header.startsWith(TOKEN_PREFIX)) {
chain.doFilter(req, res);
return;
}
UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader(HEADER_STRING);
if (token != null) {
String user = JWT.require(Algorithm.HMAC512(SECRET.getBytes()))
.build()
.verify(token.replace(TOKEN_PREFIX, ""))
.getSubject();
if (user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}
return null;
}
return null;
}
}
If you are extending your JWTAuthenticationFilter from AbstractAuthenticationProcessingFilter, you can override unsuccessfulAuthenticationlike below:
#Override
protected void unsuccessfulAuthentication(
HttpServletRequest request, HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException {
SecurityContextHolder.clearContext();
failureHandler.onAuthenticationFailure(request, response, failed);
}
Now, as you can see, I've delegated the failure processing to my failureHandler which is of type org.springframework.security.web.authentication.AuthenticationFailureHandler.
For this you need to register your custom failure handler. You can do that by implementing your handler from org.springframework.security.web.authentication.AuthenticationFailureHandler and override onAuthenticationFailure, with checking the instance of your exception thrown from JWTAuthenticationFilter, like this:
#Component
public class MyAuthFailureHandler implements AuthenticationFailureHandler {
private final ObjectMapper mapper;
#Autowired
public MyAuthFailureHandler(ObjectMapper mapper) {
this.mapper = mapper;
}
#Override
public void onAuthenticationFailure(
HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
throws IOException, ServletException {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
if (e instanceof BadCredentialsException) {
mapper.writeValue(
response.getWriter(),
ErrorResponse.of(
"Invalid username or password", ErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
} else if (e instanceof JwtExpiredTokenException) {
mapper.writeValue(
response.getWriter(),
ErrorResponse.of(
"Token has expired", ErrorCode.JWT_TOKEN_EXPIRED, HttpStatus.UNAUTHORIZED));
} else if (e instanceof AuthMethodNotSupportedException) {
mapper.writeValue(
response.getWriter(),
ErrorResponse.of(e.getMessage(), ErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
} else if (e instanceof TokenEncryptionException) {
mapper.writeValue(
response.getWriter(),
ErrorResponse.of(e.getMessage(), ErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
} else if (e instanceof InvalidJwtAuthenticationTokenException) {
mapper.writeValue(
response.getWriter(),
ErrorResponse.of(e.getMessage(), ErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
}
mapper.writeValue(
response.getWriter(),
ErrorResponse.of(
"Authentication failed", ErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
}
I am new to JWT. I create my own microservice and want to introduce JWT authentication. I have one website that issues a token and in the other I want to check the correctness of this token. I want to do this without connecting the second site to db. This approach seems to me appropriate and best for user data.
I have following payload of token:
{
"sub": "Marek",
"auth": [
{
"authority": "ROLE_USER"
}
],
"iat": 1574091010,
"exp": 1574091210
}
Its my code:
WebSecurityConfig
#Autowired
private JwtTockenCreator jwtTockenCreator;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests()
.antMatchers("/user/login").permitAll()
.antMatchers("/user/addUser").permitAll()
.anyRequest()
.authenticated();
http.exceptionHandling().accessDeniedPage("/login");
http.apply(new JWTConfigurer(jwtTockenCreator));
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
}
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
JwtTokenFilter
public class JwtTokenFilter extends OncePerRequestFilter {
private JwtTockenCreator jwtTockenCreator;
public JwtTokenFilter(JwtTockenCreator jwtTockenCreator) {
this.jwtTockenCreator = jwtTockenCreator;
}
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
FilterChain filterChain) throws ServletException, IOException {
String token = jwtTockenCreator.resolveToken(httpServletRequest);
try {
if (token != null && jwtTockenCreator.validateToken(token)) {
Authentication auth = jwtTockenCreator.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (CustomException ex) {
// this is very important, since it guarantees the user is not authenticated at
// all
SecurityContextHolder.clearContext();
httpServletResponse.sendError(ex.getHttpStatus().value(), ex.getMessage());
return;
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
JwtTockenCreator
public class JwtTockenCreator {
#Value("${security.secretKey}")
private String secretKey;
#PostConstruct
protected void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}
public Authentication getAuthentication(String token) {
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody();
Collection<? extends GrantedAuthority> authorities = Arrays.asList(claims.get(secretKey).toString().split(",")).stream()
.map(authority -> new SimpleGrantedAuthority(authority)).collect(Collectors.toList());
User principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken( principal,"",authorities);
}
public String getUsernameFromToken(String token) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}
public String resolveToken(HttpServletRequest req) {
String bearerToken = req.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
throw new CustomException("Expired or invalid JWT token", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
JWTConfigurer
public class JWTConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private JwtTockenCreator jwtTockenCreator;
public JWTConfigurer(JwtTockenCreator jwtTockenCreator) {
this.jwtTockenCreator = jwtTockenCreator;
}
#Override
public void configure(HttpSecurity http) throws Exception {
JwtTokenFilter customFilter = new JwtTokenFilter(jwtTockenCreator);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
UserController
#CrossOrigin
#RestController
#RequestMapping("/user")
public class UserController {
#Autowired
RestTemplate restTemplate;
#Value("${hostname}")
public String hostname;
#Value("${user.port}")
public String userPort;
#PostMapping("/login")
public ResponseEntity<String> login(#RequestBody User user) {
String urlUser = hostname + userPort + "/user/login";
String token = restTemplate.postForObject(urlUser, user, String.class);
return ResponseEntity.ok(token);
}
#PreAuthorize("hasRole('USER')")
#PostMapping("/addUser")
public ResponseEntity<String> registerAction(#RequestBody User user) {
String urlUser = hostname + userPort + "/user/addUser";
String token = restTemplate.postForObject(urlUser, user, String.class);
return ResponseEntity.ok(token);
}
}
In Eclipse doesn't give any errors. That's why I don't know what I'm doing wrong
when I want to call / user / addUser and add a new user nothing happens. In the User service I call, I have a function responsible for adding users and it works correctly when I refer to it directly. And if I want to do it through my Rest Api it doesn't work anymore. And it is my problem that I do not know what can happen because I have no mistake. I remind you that I am still learning and I am asking for understanding
I am writing rest service with json. For backend I use Spring Security. I have form witch sends with ajax rest object as follow:
{email: "admin", password: "secret"}
Now on the server I have configuration as follow:
#Configuration
#EnableWebSecurity
#ComponentScan("pl.korbeldaniel.cms.server")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
#Autowired
private RestAuthenticationSuccessHandler authenticationSuccessHandler;
#Autowired
private RestAuthenticationFailureHandler authenticationFailureHandler;
#Bean
JsonAuthenticationFilter jsonAuthenticationFilter() throws Exception {
JsonAuthenticationFilter filter = new JsonAuthenticationFilter();
filter.setAuthenticationManager(authenticationManagerBean());
System.out.println("jsonAuthenticationFilter");
return filter;
}
#Bean
public RestAuthenticationSuccessHandler mySuccessHandler() {
return new RestAuthenticationSuccessHandler();
}
#Override
#Autowired
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("admin").password("secret").roles("ADMIN");
// auth.jdbcAuthentication().
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(jsonAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
http.csrf().disable();//
http.exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint)//
.and().authorizeRequests()//
.antMatchers("/").permitAll()//
.antMatchers("/services/anonymous/**").permitAll()//
.antMatchers("/services/authenticated/**").authenticated()//
.and().formLogin().loginProcessingUrl("/services/anonymous/loginService/login").usernameParameter("email").passwordParameter("password")//
.successHandler(authenticationSuccessHandler)//
.and().logout().logoutUrl("/services/anonymous/loginService/logout");
// http.httpBasic();
}
}
Problem is that spring security demands from me to send credentials as body, but I would like to spring accept my Json object.
So I've wrote my own authentication filter base on this:
#Component
public class JsonAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private boolean postOnly;
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
System.out.println("attemptAuthentication");
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
UsernamePasswordAuthenticationToken authRequest = this.getUserNamePasswordAuthenticationToken(request);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
/**
* #param request
* #return
*/
private UsernamePasswordAuthenticationToken getUserNamePasswordAuthenticationToken(HttpServletRequest request) {
// TODO Auto-generated method stub
System.out.println(request);
return null;
}
}
But unfortunatelly this filter seems to not work.
When I send ajax post request from login form, I am getting 302 Found and then I am getting this:
Remote Address:127.0.0.1:8080
Request URL:http://localhost:8080/cms/login?error
Request Method:GET
Status Code:404 Not Found
Like there fail to validate user credential (cause form body is empty and credentials goes as json), and then it redirect to login?error which doesn't exist cause I've my own login form.
Please help.
Edit
public class WebServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { SecurityConfig.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebConfig.class };
}
#Override
protected String[] getServletMappings() {
// return new String[] { "/" };
// return new String[] { "/cms/" };
return new String[] { "/services/*" };
}
}
#EnableWebMvc
#ComponentScan(basePackages = "pl.daniel.cms.server")
public class WebConfig extends WebMvcConfigurerAdapter {
}
Well, it must not work until you write the getUserNamePasswordAuthenticationToken body.
Actually, you must read the request body of the HttpServletRequest, parse it through Jackson or any other mapping way and create with it the UsernamePasswordAuthenticationToken.
Using Jackson (choose the right version depending on your Spring version), I would create a simple bean like this:
#JsonIgnoreProperties(ignoreUnkown=true)
public LoginRequest{
private String email;
private String password;
// getters & setters
}
The use it to map it the request body:
private UsernamePasswordAuthenticationToken getUserNamePasswordAuthenticationToken(HttpServletRequest request) throws IOException{
StringBuffer sb = new StringBuffer();
BufferedReader bufferedReader = null;
String content = "";
LoginRequest sr = null;
try {
bufferedReader = request.getReader()
char[] charBuffer = new char[128];
int bytesRead;
while ( (bytesRead = bufferedReader.read(charBuffer)) != -1 ) {
sb.append(charBuffer, 0, bytesRead);
}
content = sb.toString();
ObjectMapper objectMapper = new ObjectMapper();
try{
sr = objectMapper.readValue(content, LoginRequest.class);
}catch(Throwable t){
throw new IOException(t.getMessage(), t);
}
} catch (IOException ex) {
throw ex;
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
throw ex;
}
}
}
return new UsernamePasswordAuthenticationToken(sr.getEmail(), sr.getPassword());
}
P.D. Yo must use Post, you will never be able to post a request-body using GET
You can extend and override WebSecurityConfigurerAdapter
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.addFilter(new UserNamePasswordAuthFilter(authenticationManager(), userRepo))
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService); // custom user service
provider.setPasswordEncoder(passwordEncoder); // custom password encoder
return provider;
}
Then you can define a filter for authentication and optionally you can override successful login behavior.
public class UserNamePasswordAuthFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authManager;
private final AecUserRepo userRepo;
public UserNamePasswordAuthFilter(AuthenticationManager authManager, AecUserRepo userRepo) {
super();
this.authManager = authManager;
this.userRepo = userRepo;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
try {
// Get username & password from request (JSON) any way you like
UsernamePassword authRequest = new ObjectMapper()
.readValue(request.getInputStream(), UsernamePassword.class);
Authentication auth = new UsernamePasswordAuthenticationToken(authRequest.getUsername(),
authRequest.getPassword());
return authManager.authenticate(auth);
} catch (Exception exp) {
throw new RuntimeException(exp);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
+ authResult);
}
// custom code
SecurityContextHolder.getContext().setAuthentication(authResult);
}
}