I want to write a simple test but my SecurityConfig gives me always Access Denied. Here is my config:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new TokenAuthenticationFilter(userService), AnonymousAuthenticationFilter.class);
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
//Options preflight
http.authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll();
//ACL
http.authorizeRequests().antMatchers(HttpMethod.POST, "/auth/password").hasAnyRole("ADMIN", "CUSTOMER", "REFUGEE");
http.authorizeRequests().antMatchers("/auth/**").anonymous();
//Tags
http.authorizeRequests().antMatchers(HttpMethod.GET, "/tags").hasAnyRole("ADMIN", "CUSTOMER", "REFUGEE");
http.authorizeRequests().antMatchers(HttpMethod.GET, "/tags/recommendation").hasAnyRole("ADMIN", "CUSTOMER", "REFUGEE");
http.authorizeRequests().antMatchers(HttpMethod.POST, "/tags").hasAnyRole("ADMIN", "REFUGEE");
http.authorizeRequests().antMatchers(HttpMethod.GET, "/tags/recommendation/random").hasAnyRole("ADMIN", "CUSTOMER", "REFUGEE");
http.authorizeRequests().antMatchers(HttpMethod.PUT, "/tags").hasAnyRole("ADMIN");
http.authorizeRequests().antMatchers(HttpMethod.GET, "/tags/notapproved").hasAnyRole("ADMIN");
http.authorizeRequests().antMatchers(HttpMethod.PUT, "/tags/approve/{id}").hasAnyRole("ADMIN");
}
And the AuthenticationFilter:
public class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private UserService userService;
public TokenAuthenticationFilter(UserService userService) {
super("/");
this.userService = userService;
}
private final String HEADER_SECURITY_TOKEN = "Authorization";
private final String PARAMETER_SECURITY_TOKEN = "access_token";
private String token = "";
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
this.token = request.getHeader(HEADER_SECURITY_TOKEN);
if ("".equals(this.token) || this.token == null) {
this.token = request.getParameter(PARAMETER_SECURITY_TOKEN);
}
//Attempt to authenticate
Authentication authResult;
authResult = attemptAuthentication(request, response);
if (authResult == null) {
chain.doFilter(request, response);
} else {
successfulAuthentication(request, response, chain, authResult);
}
}
/**
* Attempt to authenticate request - basically just pass over to another
* method to authenticate request headers
*/
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
Authentication userAuthenticationToken = authUserByToken();
if (userAuthenticationToken == null) {
//throw new AuthenticationServiceException(MessageFormat.format("Error | {0}", "Bad Token"));
}
return userAuthenticationToken;
}
/**
* authenticate the user based on token, mobile app secret & user agent
*
* #return
*/
private Authentication authUserByToken() {
Authentication securityToken = null;
try {
User user = userService.findUserByAccessToken(this.token);
securityToken = new PreAuthenticatedAuthenticationToken(
user, null, user.getAuthorities());
} catch (Exception e) {
logger.error("Authenticate user by token error: ", e);
}
return securityToken;
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
chain.doFilter(request, response);
}
And the simple test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath*:spring/test/test-servlet.xml"})
#WebAppConfiguration
public class TagControllerTest {
private MockMvc mockMvc;
private TagService tagServiceMock;
#Autowired
private FilterChainProxy springSecurityFilterChain;
#Resource
private WebApplicationContext webApplicationContext;
#Before
public void setUp() {
tagServiceMock = mock(TagService.class);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext)
.addFilters(this.springSecurityFilterChain)
.build();
}
public TagControllerTest() {
}
/**
* Test of getAllTags method, of class TagController.
*/
#Test
public void testGetAllTags() throws Exception {
List<Tags> tagsList = new ArrayList<>();
tagsList.add(new Tags((long) 1, "test", 2, 1));
tagsList.add(new Tags((long) 2, "test2", 4, 0));
User user = TestUtil.EMPTY_USER;
String query = "";
String notIncluded = "";
Integer limit = 8;
Integer start = 0;
Language language = new Language(0);
Boolean approved = false;
when(tagServiceMock.findTagsByQuery(user, query, limit, start, language, approved)).thenReturn(tagsList);
when(tagServiceMock.findTags(user, limit, start, language)).thenReturn(tagsList);
SecurityContextHolder.getContext().setAuthentication(TestUtil.getPrincipal("ROLE_ADMIN"));
mockMvc.perform(get("/tags").with(securityContext(SecurityContextHolder.getContext())).header("host", "localhost:80"))
.andExpect(status().isOk())
.andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8))
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0].id", is(1)))
.andExpect(jsonPath("$[1].id", is(2)))
.andExpect(jsonPath("$[0].label", is("test")))
.andExpect(jsonPath("$[1].label", is("test2")))
.andExpect(jsonPath("$[0].count", is(2)))
.andExpect(jsonPath("$[1].count", is(4)))
.andExpect(jsonPath("$[0].approved", is(1)))
.andExpect(jsonPath("$[1].approved", is(0)));
verify(tagServiceMock, times(1)).findTagsByQuery(user, query, limit, start, language, approved);
verifyNoMoreInteractions(tagServiceMock);
}
The problem is, that I always get a 403 -> Access Denied. It is because of the TokenAuthenticationFilter. If i set a header named "Authorization" with an accesstoken that is correct (correct means that is is used by an actual user) then it works. But i guess thats not how it should be for unit tests. So what to do? How to set a Role and pass the SecurityFilter?
You should turn off Spring security for the purpose of the tests if you are not doing integration tests which should include it.
Related
This is how my microservices classes are. I have two questions.
Firstly, when I run the microservice locally, the swagger document does not open automatically. When I enter the link in the form of host/v2/api-docs with my hand, it opens as json, but the ui part does not come. I can edit and view it with the swagger editor. I added dependency to pom.xml for the UI part, but it doesn't work, how to open the UI screen?
Secondly,
Except for host/v2/api-docs, when I type a link to a controller specifically, I get a 403 authorization error. This is the most important problem that I want to overcome, how can I do it? can you help me?
link/swagger-ui.html#!/signin
localhost:8000/swagger-ui.html#!/signin
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Thu Jun 23 16:04:09 TRT 2022
There was an unexpected error (type=Forbidden, status=403).
Access Denied
My pom.xml is :
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${springfox.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${springfox.version}</version>
</dependency>
<dependency>
My SwaggerConfig class is:
#Configuration
#EnableSwagger2
public class SwaggerConfig {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any()).build().apiInfo(metaData());
}
#Bean
public UiConfiguration uiConfiguration() {
return UiConfigurationBuilder.builder().deepLinking(true).validatorUrl(null).build();
}
private static final Contact DEFAULT_CONTACT = new Contact("Rosaline Fox,Anna Hurt", "http://www.google.com",
"rosaline.fox#gmail.com,anna.hurt#gmail.com");
private ApiInfo metaData() {
return new ApiInfoBuilder().title("Auth Service Controller API Title")
.description("Auth Service Controller API Description").version("1.0")
.license("Apache License Version 2.0").licenseUrl("https://www.apache.org/licenses/LICENSE-2.0")
.contact(DEFAULT_CONTACT).build();
}
}
My WebSecurityConfig class is:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String XI_PARTNER = "XIPartner";
private static final String XI_CONSULTANT = "XIConsultant";
private static final String SALES = "Sales";
private static final String STANDART = "Standart";
public static final String ADMIN = "Admin";
#Autowired
private JwtTokenProvider jwtTokenProvider;
#Autowired
private FilterChainExceptionHandler filterChainExceptionHandler;
#Autowired
private HandlerExceptionResolver handlerExceptionResolver;
#Override
protected void configure(HttpSecurity http) throws Exception {
// Disable CSRF (cross site request forgery)
http.csrf().disable();
http.cors().configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues());
// No session will be created or used by spring security
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(filterChainExceptionHandler, LogoutFilter.class);
http.exceptionHandling().accessDeniedHandler((req, res, e) -> handlerExceptionResolver.resolveException(req, res, null, e));
// Entry points
http.authorizeRequests()
.antMatchers("/**/signin/otp", "/**/signin/**", "/**/v2/api-docs/**", "/**/swagger-ui.html#/**").permitAll()
.antMatchers("/**/customers/create").hasAnyAuthority(SALES)
.antMatchers("/**/customers/update").hasAnyAuthority(SALES)
.antMatchers("/**/customers/all").hasAnyAuthority(SALES)
.antMatchers("/**/customers/deactivate").hasAnyAuthority(SALES)
.antMatchers("/**/customers/reactivate").hasAnyAuthority(SALES)
.antMatchers("/**/products/create").hasAnyAuthority(SALES)
.antMatchers("/**/products/update").hasAnyAuthority(SALES)
.antMatchers("/**/users/create").hasAnyAuthority(SALES)
.antMatchers("/**/users/update").hasAnyAuthority(SALES)
.antMatchers("/**/users/deactivate").hasAnyAuthority(SALES)
.antMatchers("/**/users/reactivate").hasAnyAuthority(SALES)
.antMatchers("/**/admin/user/all").hasAnyAuthority(ADMIN)
.antMatchers("/**/xicustomers/create").hasAnyAuthority(SALES)
.antMatchers("/**/xicustomers/update").hasAnyAuthority(SALES)
.antMatchers("/**/xicustomers/all").hasAnyAuthority(SALES)
.antMatchers("/**/partner/create").hasAnyAuthority(SALES)
.antMatchers("/**/xicustomers/list").hasAnyAuthority(XI_PARTNER,XI_CONSULTANT)
.antMatchers("/**/report/list/**").hasAnyAuthority(XI_CONSULTANT)
.antMatchers("/**/originator").hasAnyAuthority(STANDART)
.antMatchers("/**/blackhour/add").hasAnyAuthority(STANDART)
.antMatchers("/**/blackhour").hasAnyAuthority(STANDART)
.antMatchers("/**/access/**").anonymous()
.antMatchers("/**/pwd/forgot").anonymous()
.antMatchers("/**/maximo").anonymous()
.anyRequest().authenticated();
// Apply JWT
http.apply(new JwtTokenFilterConfigurer(jwtTokenProvider));
// Optional, if you want to test the API from a browser
// http.httpBasic();
}
#Override
public void configure(WebSecurity web) throws Exception {
// Allow eureka client to be accessed without authentication
web.ignoring().antMatchers("/*/")//
.antMatchers("/eureka/**")//
.antMatchers(HttpMethod.OPTIONS, "/**"); // Request type options
// should be
// allowed.
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
#Bean
public AuthenticationManager customAuthenticationManager() throws Exception {
return authenticationManager();
}
}
My Controller Class :
#ApiOperation(value = "Login Service", notes = "Login service with captcha verification.")
#PostMapping("/signin")
#ResponseBody
public ResponseEntity<LoginResponse> login(#RequestBody LoginRequest loginRequest) {
LoginResponse loginResponse = this.loginService.login(loginRequest);
return ResponseEntity.accepted().body(loginResponse);
}
My JwtTokenFilterConfigurer class :
public class JwtTokenFilterConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private JwtTokenProvider jwtTokenProvider;
public JwtTokenFilterConfigurer(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
#Override
public void configure(HttpSecurity http) throws Exception {
JwtTokenFilter customFilter = new JwtTokenFilter(this.jwtTokenProvider);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
My JwtTokenFilter class :
#Slf4j
#Component
public class JwtTokenFilter extends GenericFilterBean {
private JwtTokenProvider jwtTokenProvider;
public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
//HttpServletResponse response = (HttpServletResponse) res;
String requestURI = request.getRequestURI();
String token = getBearerToken((HttpServletRequest) req);
if (token != null && !requestURI.contains("/signin/otp")) {
TokenParams params = null;
try {
params = this.jwtTokenProvider.validateToken(token);
} catch (JwtException | IllegalArgumentException e) {
log.warn("Invalid Token: {}, Error: {}", params, e.getMessage());
//response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "INVALID JWT token");
//return;
throw new UnauthorizedException();
}
if (!params.getRoles().contains(WebSecurityConfig.ADMIN) && params.isForOtp() == true) {
log.warn("Invalid Token: {}, it is for OTP!", params);
//response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "INVALID JWT token");
//return;
throw new UnauthorizedException();
}
Authentication auth = this.jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
HeaderMapRequestWrapper wrappedRequest = new HeaderMapRequestWrapper(request);
wrappedRequest.addHeader("companyId", params.getCompanyId());
wrappedRequest.addHeader("user", params.getEmail());
filterChain.doFilter(wrappedRequest, res);
} else {
filterChain.doFilter(req, res);
}
}
private static final String AUTHORIZATION = "Authorization";
private String getBearerToken(HttpServletRequest req) {
String bearerToken = req.getHeader(AUTHORIZATION);
/*
* if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
* return bearerToken.substring(7, bearerToken.length()); }
*/
if (bearerToken != null) {
return bearerToken;
}
return null;
}
}
Getting 401 for "/register" endpoint even after defining it as permitAll in antMatcher.
I am new to spring security.
I have configured two endpoints "/login" and "/register". I want "/register" to be available public and there should be a username/password check on accessing "/login".
My server.servlet.context-path is set to "/todo/user".
I tried to search for the solution but nothing would explain why i am facing the issue or may be i wasnt able to comprehend it being new to this.
Controller class
#Controller
#RequestMapping(value = "/register")
public class RegistrationController {
#Autowired
UserService userService;
#RequestMapping(value = "", method = RequestMethod.POST,
produces = "application/json")
#ResponseBody
public ResponseEntity<String> registration(#RequestBody #Valid UserRegistration userRegistration) {
Optional<UserRegistration> user = Optional.ofNullable(userRegistration);
if(!user.isPresent()) {
throw new UserDataNotAvailableException("User data is not available");
}
//TO-DO Write implementation of isUserValid
Optional<Boolean> isRegistrationSuccess = user.map(userService::registerNewUser);
return new ResponseEntity<String>("This is temp", HttpStatus.OK);
}
}
SecurityConfig
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(HttpMethod.POST, "/register").permitAll()
.anyRequest().authenticated()
.and().addFilter(new
JwtAuthenticationFilter(authenticationManager()));
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
}
Authentication Filter
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private static final int EXPIRATION_TIME = 3900;
private static final String HEADER_NAME = "auth";
private AuthenticationManager authenticationManager;
#Value("${jwt.secret}")
private String secret;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
try{
UserLogin user = new ObjectMapper().readValue(request.getInputStream(), UserLogin.class);
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getEmailId(), user.getPassword(),
new ArrayList<>()));
}catch (IOException e) {
throw new InternalServerException("There was a problem in processing the request");
}
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication authResult) throws IOException, ServletException {
String jwt = Jwts.builder().setSubject(((UserLogin)authResult.getPrincipal()).getEmailId())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.ES256, secret)
.compact();
response.addHeader(HEADER_NAME, jwt);
}
}
AS evident from security config class i have permitted all to access my "/register" endpoint.
But when i make a request on "/register" i get a 401.
This should not be the case and i should be able to access this particular endpoint without any JWT
I have created a REST API that require a authentication with JWT.
My implementation is very similar with the code found on https://auth0.com/blog/securing-spring-boot-with-jwts/
When I try to return the current user, I always receive a null return.
My code:
Websecurity:
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
// login
.antMatchers(HttpMethod.POST, "/login")
.permitAll()
.anyRequest()
.authenticated()
.and()
.addFilterBefore(new JWTLoginFilter(
"/login", authenticationManager(), logService), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
JWTAuthenticationFilter:
public class JWTAuthenticationFilter extends GenericFilterBean {
#Override
public void doFilter(
ServletRequest req,
ServletResponse res,
FilterChain filterChain) throws IOException, ServletException {
Authentication authentication = TokenAuthenticationService.getAuthentication((HttpServletRequest)req);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(req, res);
}
}
I don't included all the code of JWT authentication, because JWT is working ok, user access too.
I believe the problem is in the filter or some configuration.
Then, I made a facade to get the current user on a service or controller, with the following code (method 4 on http://www.baeldung.com/get-user-in-spring-security):
public Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
but this don't worked.
- SecurityContextHolder.getContext() returned org.springframework.security.core.context.SecurityContextImpl#ffffffff: Null authentication.
- SecurityContextHolder.getContext().getAuthentication() returned null object.
Update (and solution):
In my controller, if I use this code:
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
I can get the current user, but, in my service, the exact same code don't work.
But then, I remember that SecurityContext is "lost" on another thread (source: https://docs.spring.io/spring-security/site/docs/current/reference/html/concurrency.html), and my service is async
#Async
public CompletableFuture<Optional<ViewUserDto>> findByLogin(String login) throws InterruptedException {
...
}
So, using the code found here: https://stackoverflow.com/a/40347437/4794469, everything works correctly.
I don't known if this can bring any side effects for my code yet (all unit tests worked)
I have worked in an application that has a similar authorization flow as yours:
WebSecurityConfigurerAdapter
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationProvider provider;
#Autowired
private TokenAuthenticationService tokenService;
#Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.authenticationProvider(provider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().disable();
http.csrf().disable();
http.authorizeRequests().antMatchers(HttpMethod.POST, "/v1/users", "/v1/oauth/token").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new OAuthTokenFilter("/v1/oauth/token", authenticationManager(), tokenService), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new AuthorizationFilter(tokenService), UsernamePasswordAuthenticationFilter.class);
}
}
AbstractAuthenticationProcessingFilter
public class OAuthTokenFilter extends AbstractAuthenticationProcessingFilter {
private final ObjectMapper MAPPER = new ObjectMapper();
private TokenAuthenticationService service;
public OAuthTokenFilter(String url, AuthenticationManager manager, TokenAuthenticationService service) {
super(new AntPathRequestMatcher(url));
setAuthenticationManager(manager);
this.service = service;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
Login login = MAPPER.readValue(request.getInputStream(), Login.class);
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(login.getUsername(), login, Arrays.asList());
return getAuthenticationManager().authenticate(token);
}
#Override
protected void successfulAuthentication(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authentication) throws IOException, ServletException {
User credentials = (User) authentication.getPrincipal();
String token = service.jwt(credentials);
String json = MAPPER.writeValueAsString(new AuthorizationToken(token, "Bearer"));
response.addHeader("Content-Type", "application/json");
response.getWriter().write(json);
response.flushBuffer();
}
}
GenericFilterBean
public class AuthorizationFilter extends GenericFilterBean {
private TokenAuthenticationService service;
public AuthorizationFilter(TokenAuthenticationService service) {
this.service = service;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
Authentication authentication = service.getAuthentication((HttpServletRequest)request);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
}
TokenAuthenticationService
#Service
public class TokenAuthenticationService {
public static final String JWT_SECRET_ENV = "JWT_SECRET";
public static final String ISSUER = "my issuer";
public static final String ROLE_CLAIM = "role";
public static final String THIRDY_PARTY_ID_CLAIM = "thirdy_party_id";
public static final String TOKEN_PREFIX = "Bearer";
public static final String HEADER = "Authorization";
#Autowired
private Environment environment;
public Authentication getAuthentication(HttpServletRequest request) {
String token = request.getHeader(HEADER);
String secret = environment.getProperty(JWT_SECRET_ENV);
if (token != null) {
try {
String bearer = token.replace(TOKEN_PREFIX, "").trim();
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer(ISSUER)
.build();
DecodedJWT jwt = verifier.verify(bearer);
User user = new User();
user.setId(jwt.getSubject());
user.setThirdPartyId(jwt.getClaim(THIRDY_PARTY_ID_CLAIM).asString());
user.setRole(jwt.getClaim(ROLE_CLAIM).asString());
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRole());
return new UsernamePasswordAuthenticationToken(user, null, authorities);
} catch (Exception e){
e.printStackTrace(System.out);
}
}
return null;
}
}
And then, the controller:
#RestController
public class UserController {
#ResponseBody
#GetMapping("/v1/users/{id}")
#PreAuthorize("hasAuthority('USER')")
public User get(#PathVariable("id") String id, Authentication authentication) {
User user = (User) authentication.getPrincipal();
return user;
}
}
I faced similar issue when i was enabling JWT on my web app.
You need: "Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files".
Please download the this package from the below URL and replace US_export_policy.jar, local_policy.jar (\jre\lib\security)
If it is still not working, then you need to replace the above jar files in the location \lib\security
http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html
I have rest api application in Spring Boot 1.5.3, I'm using security to login and authenticate every request by token to my api. I want add my custom exception with unauthorized exception when user not found by token. Class with exception is added but every response has 500 code but I want 401 response code. Belowe is my code.
StatelessAuthenticationFilter
public class StatelessAuthenticationFilter extends GenericFilterBean {
private final TokenAuthenticationService tokenAuthenticationService;
public StatelessAuthenticationFilter(TokenAuthenticationService taService) {
this.tokenAuthenticationService = taService;
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(tokenAuthenticationService.getAuthentication((HttpServletRequest) req));
chain.doFilter(req, res);
}
StatelessLoginFilter
public class StatelessLoginFilter extends AbstractAuthenticationProcessingFilter {
private final TokenAuthenticationService tokenAuthenticationService;
private final UserServiceImpl userService;
public StatelessLoginFilter(String urlMapping, TokenAuthenticationService tokenAuthenticationService,
UserServiceImpl userDetailsService, AuthenticationManager authManager) {
super(new AntPathRequestMatcher(urlMapping));
this.userService = userDetailsService;
this.tokenAuthenticationService = tokenAuthenticationService;
setAuthenticationManager(authManager);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
String headerCredentials = request.getHeader("BasicAuth");
if (headerCredentials == null) {
throw new BadCredentialsException("No header in request");
}
String credentials = new String(Base64.decodeBase64(headerCredentials), "UTF-8");
if (!credentials.contains((":"))) {
throw new BadCredentialsException("Wrong header");
}
String [] credentialsArray = credentials.split(":");
String login = credentialsArray[0];
String password = credentialsArray[1];
final UsernamePasswordAuthenticationToken loginToken = new UsernamePasswordAuthenticationToken(login, password);
return getAuthenticationManager().authenticate(loginToken);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication authentication) throws IOException, ServletException {
// Lookup the complete User2 object from the database and create an Authentication for it
final User authenticatedUser = userService.loadUserByUsername(authentication.getName());
final UserAuthentication userAuthentication = new UserAuthentication(authenticatedUser);
// Add the custom token as HTTP header to the response
tokenAuthenticationService.addAuthentication(response, userAuthentication);
// Add the authentication to the Security context
SecurityContextHolder.getContext().setAuthentication(userAuthentication);
}
MyOwnException
public class MyOwnException extends RuntimeException {
public MyOwnException(String message) {
super(message);
}
RestResponseEntityExceptionHandler
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends DefaultHandlerExceptionResolver {
#ExceptionHandler(MyOwnException.class)
void handleMyOwnException(HttpServletResponse response) throws IOException {
response.sendError(HttpStatus.UNAUTHORIZED.value());
}
}
StatelessAuthenticationSecurityConfig
#EnableWebSecurity
#Configuration
#Order(1)
public class StatelessAuthenticationSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserServiceImpl userService;
#Autowired
private TokenAuthenticationService tokenAuthenticationService;
public StatelessAuthenticationSecurityConfig() {
super(true);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().hasRole("USER")
.anyRequest().hasRole("ADMIN").and()
// custom JSON based authentication by POST of {"username":"<name>","password":"<password>"} which sets the token header upon authentication
.addFilterBefore(new StatelessLoginFilter("/login", tokenAuthenticationService, userService, authenticationManager()), UsernamePasswordAuthenticationFilter.class)
// custom Token based authentication based on the header previously given to the client
.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService), UsernamePasswordAuthenticationFilter.class);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.addAllowedOrigin("*");
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "DELETE", "OPTIONS"));
configuration.setExposedHeaders(Arrays.asList("x-auth-token"));
configuration.addAllowedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
#Override
protected UserServiceImpl userDetailsService() {
return userService;
}
VoteApp
#SpringBootApplication
public class VoteApp {
public static void main(String[] args) {
SpringApplication.run(VoteApp.class, args);
}
#Bean
public Filter characterEncodingFilter() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceEncoding(true);
return characterEncodingFilter;
}
}
UserServiceImpl
#Service
public class UserServiceImpl implements org.springframework.security.core.userdetails.UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public final User loadUserByUsername(String username) throws UsernameNotFoundException {
final User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("user not found");
}
return user;
}
public User findByToken(String token) throws MyOwnException {
final User user = userRepository.findByToken(token);
if (user == null) {
throw new MyOwnException("user by token not found");
}
return user;
}
public void save(User user) {
userRepository.save(user);
}
}
Obviously #ControllerAdvice can't handle your exception because controller methods has not been called yet. I mean you exception being thrown in servlet filter. I think you going to have to catch it manually, smth like this:
public class StatelessAuthenticationFilter extends GenericFilterBean {
private final TokenAuthenticationService tokenAuthenticationService;
public StatelessAuthenticationFilter(TokenAuthenticationService taService) {
this.tokenAuthenticationService = taService;
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
Authentication auth = null;
try {
auth = tokenAuthenticationService.getAuthentication((HttpServletRequest) req);
} catch (MyOwnException e) {
SecurityContextHolder.clearContext();
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
return;
}
SecurityContextHolder.getContext().setAuthentication(auth);
chain.doFilter(req, res);
}
Add #ResponseStatus annotation to your exception handler of controller advice.
For more information visit - Exception Handling in Spring MVC
I am trying to secure my Spring Rest API with token here is my custom filter
public class CustomTokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final Logger logger = LoggerFactory.getLogger(CustomTokenAuthenticationFilter.class);
public CustomTokenAuthenticationFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
super.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(defaultFilterProcessesUrl));
setAuthenticationManager(new NoOpAuthenticationManager());
setAuthenticationSuccessHandler(new TokenSimpleUrlAuthenticationSuccessHandler());
}
public final String HEADER_SECURITY_TOKEN = "X-CustomToken";
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
String token = request.getHeader(HEADER_SECURITY_TOKEN);
logger.info("token found:"+token);
AbstractAuthenticationToken userAuthenticationToken = authUserByToken(token);
if(userAuthenticationToken == null || userAuthenticationToken.getPrincipal().equals("guest")) throw new AuthenticationServiceException(MessageFormat.format("Error | {0}", "Bad Token"));
return userAuthenticationToken;
}
/**
* authenticate the user based on token
* #return
*/
private AbstractAuthenticationToken authUserByToken(String token) {
if(token==null) {
return null;
}
AbstractAuthenticationToken authToken = new MyToken(token);
try {
return authToken;
} catch (Exception e) {
logger.error("Authenticate user by token error: ", e);
}
return authToken;
}
#Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
super.doFilter(req, res, chain);
}
}
and here is how I configured it
#EnableWebSecurity
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
protected AbstractAuthenticationProcessingFilter getFilter() {
return new CustomTokenAuthenticationFilter("/api/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(getFilter(), UsernamePasswordAuthenticationFilter.class)
.csrf().disable();
}
}
If you look at the getFilter(), I have passed "/api/*" as a filter processing url, but I want to configure these urls with HttpSecurity object, some thing as follows
http.authorizeRequests().antMatchers("/", "/rome").permitAll()
.antMatchers("/api/admin", "/api/newUser").access("hasRole('ADMIN')")
.antMatchers("/api/db").access("hasRole('ADMIN') or hasRole('DBA')")
Problem I see is that, the Custom filter requires a String as "filter processing url" but I do not want specify anything. That information should be passed by configuring HttpSecurity object through antMatchers etc.
Is it really possible? if yes how can I achieve that?
I used OncePerRequestFilter.
public class MyAuthenticationFilter extends OncePerRequestFilter {
// private RequestMatcher requestMatcher;
private List<RequestMatcher> includedPathMatchers = new ArrayList<>();
private List<RequestMatcher> excludedPathMatchers = new ArrayList<>();
// implement getters and setters
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
// your filter implementation and security logics
}
}
You can treat this class as a normal bean (use #Autowired and so on). Then you just need do register it in your context and inject it in the security chain.
Hope it helps.
This answer will be useful to you. It says to use setter setFilterProcessingURL() available in AbstractAuthenticationProcessingFilter