I have this piece of code that calls some authorization server to get the token.
TokenRequest tokenRequest = new TokenRequest("authorization_code", code, clientId, clientSecret, redirectionUrl);
doExecute(
tokenUri,
HttpMethod.POST,
new HttpEntity(tokenRequest, HttpHeaderBuilder.builder().jsonContent().build()),
new JsonRequestListener() {
#Override
public void onSuccess(JSONObject obj) throws JSONException {
}
#Override
public void onError(JSONObject obj, StatusLine statusLine) {
}
#Override
public void onFailure(Throwable throwable) {
}
});
In doExecute method, I make this call:
ResponseEntity<String> response = restTemplate.exchange(uri, method, httpEntity, String.class);
I created an interceptor to my resttemplate and checked:
2020-03-18 15:53:28.127 INFO 29756 --- [o-8080-exec-174] n.j.a.c.v2.MyController : ===========================request begin================================================
2020-03-18 15:53:28.127 DEBUG 29756 --- [o-8080-exec-174] n.j.a.c.v2.MyController : URI : https://api-sandbox.foodics.com/oauth/token
2020-03-18 15:53:28.127 DEBUG 29756 --- [o-8080-exec-174] n.j.a.c.v2.MyController : Method : POST
2020-03-18 15:53:28.127 DEBUG 29756 --- [o-8080-exec-174] n.j.a.c.v2.MyController : Headers : {Accept=[text/plain, text/plain, application/xml, text/xml, application/json, application/*+xml, application/*+json, */*, */*], Content-Type=[application/json], Content-Length=[1117]}
2020-03-18 15:53:28.128 DEBUG 29756 --- [o-8080-exec-174] n.j.a.c.v2.MyController : Request body: {"grant_type":"authorization_code","code":"def50200f5f4d344ed8ed386a3fefd99b4977ad9fed4fcfb19a4f1923267d464bbf75a589d2c539f3880bbe051e8c40f3763d5d2505f83ad4698f5b95213117eb184909c5591496dd83e9c91bc218fde5d173e2ad09f8cacf0d408173c22f5b1704ae7127296d3a8feba50e3e416dbd928e58a071720b50d7ccf6bee7d9bb3133bb9d5284ef7027f28cd69e0b4144f151af212a7f87c3b80890ce3488a0fc9c2118f9fae4e95a16ca96007b3599ea1e8765f9db69ea524aa01b17eec3555084e139e2e914bb7922bd83057a62b4784cf1e3688ce3741ba67b47168668e8be4f701a277b24b79e36ab89f6d7bcb3a1cc1fe022437db688afbb038175cad1183c35bafc5999e43641fcebd7bcd47dfb8396300f83aa17484177acebbc3fed84d7a46f8b7895ab26d8c5c46728b92eb8ed1d3a97fdeb05915ee129d4ae0e48c4479c4ee321c103c316669d4b54213cfb12c3f97f481ce9345ba9dbef16f8c25475cd1c862547ab8ccca40c89a4ee7e6891804625941b0484fb59781d3633f59b71582c2cb7c7c22e756d728ea573547183a0cc5ee11720076f1b879fd0204ec599234b5ac412d10e4003f481e7fff9ab54374f214b9cfc","client_id":"90166b44-f4de-491a-XXX","client_secret":"f4vTOFjtxgl7Nn717JfZe15IXVsXXX","redirect_uri":"https://some_url/api/oauth"}
2020-03-18 15:53:28.128 INFO 29756 --- [o-8080-exec-174] n.j.a.c.v2.MyController : ==========================request end================================================
When I take the same request body on postman it works fine.But with my app I get 403
I found the answer here.
So I just added a User-agent header (rather than the default one java).
Related
This question already has answers here:
How to fix role in Spring Security?
(2 answers)
Closed 7 days ago.
A Spring UserDetailsService contains an admin with role ADMIN and an user with role USER:
#Bean
public UserDetailsService uds() {
UserDetails admin = User.builder()
.username("admin")
.password(encoder().encode("pwa"))
.roles("ADMIN")
.build();
UserDetails user = User.builder()
.username("user")
.password(encoder().encode("pwu"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(admin, user);
}
The repository extends CrudRepository
#RepositoryRestResource
public interface CountryRepo extends CrudRepository<Country, Long> { }
where POST requests map to the save method.
The SecurityFilterChain requires POST requests to be sent by clients with ADMIN role (see (*)):
#Bean
SecurityFilterChain configureSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authCustomizer -> authCustomizer
.requestMatchers("/api/countries").permitAll()
.requestMatchers("/h2").permitAll()
.requestMatchers(HttpMethod.POST, "/api/countries").hasRole("ADMIN") // <-- (*)
.anyRequest().authenticated()
)
.csrf().disable()
.httpBasic(withDefaults());
http.headers().frameOptions().disable();
return http.build();
}
csrf and frameOptions are needed to use the H2 console in the browser.
An admin can create countries with CURL:
C:\Users\project>curl -H "Content-Type: application/json" -u admin:pwa -X POST http://localhost:8080/api/countries -d "{\"countryCode\":\"AUS\"}"
{
"countryCode" : "AUS",
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/countries/4"
},
"country" : {
"href" : "http://localhost:8080/api/countries/4"
}
}
}
However, so can a user! I expected to get a 401 or 403 HTTP status code here.
C:\Users\mahed>curl -H "Content-Type: application/json" -u user:pwu -X POST http://localhost:8080/api/countries -d "{\"countryCode\":\"CAN\"}"
{
"countryCode" : "CAN",
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/countries/5"
},
"country" : {
"href" : "http://localhost:8080/api/countries/5"
}
}
}
Why are the POST requests by user, who does not have the ADMIN role, successful?
The requestMatcher matching the POST request needs to be before the general requestMatcher. This filter chain works:
#Bean
SecurityFilterChain configureSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authCustomizer -> authCustomizer
.requestMatchers(HttpMethod.POST, "/api/countries").hasRole("ADMIN")
.requestMatchers("/api/countries").permitAll()
.requestMatchers("/h2").permitAll()
.anyRequest().authenticated()
)
.csrf().disable()
.httpBasic(withDefaults());
http.headers().frameOptions().disable();
return http.build();
}
I have a springboot webflux project with reactive security enabled
For some reason the project seems to be calling the authenticate method of ReactiveAuthenticationManager twice (once in the default handler and once the request reaches the controller).
Here is my sample code
WebSecurityConfig.class
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
public class WebSecurityConfig {
static final String[] AUTH_WHITELIST = {
"/swagger-resources/**",
"/swagger-ui.html",
"/v2/api-docs",
"/v3/api-docs",
"/webjars/**",
"/swagger-ui/**",
"/v1/healthcheck"
};
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final AuthenticationManager authenticationManager;
private final SecurityContextRepository securityContextRepository;
public WebSecurityConfig(
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint,
AuthenticationManager authenticationManager,
SecurityContextRepository securityContextRepository) {
this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
this.authenticationManager = authenticationManager;
this.securityContextRepository = securityContextRepository;
}
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http.securityMatcher(
new NegatedServerWebExchangeMatcher(
ServerWebExchangeMatchers.pathMatchers(AUTH_WHITELIST)))
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(new HttpStatusServerAccessDeniedHandler(HttpStatus.BAD_REQUEST))
.and()
.csrf()
.disable()
.formLogin()
.disable()
.httpBasic()
.disable()
.logout()
.disable()
.authenticationManager(authenticationManager)
.securityContextRepository(securityContextRepository)
.authorizeExchange()
.pathMatchers(HttpMethod.OPTIONS)
.permitAll()
.anyExchange()
.authenticated()
.and()
.build();
}
ServerSecurityContextRepository.class
#Slf4j
#Component
public class SecurityContextRepository implements ServerSecurityContextRepository {
private final AuthenticationService authenticationService;
private final AuthenticationManager authenticationManager;
public SecurityContextRepository(
AuthenticationService authenticationService, AuthenticationManager authenticationManager) {
this.authenticationService = authenticationService;
this.authenticationManager = authenticationManager;
}
#Override
public Mono<Void> save(ServerWebExchange swe, SecurityContext sc) {
throw new UnsupportedOperationException("Not supported yet.");
}
#Override
public Mono<SecurityContext> load(ServerWebExchange swe) {
ServerHttpRequest request = swe.getRequest();
log.info("Parsing Authorization token from Request");
AuthToken authToken =
authenticationService.parseRequestToken(authenticationService.getHeaders(request));
Authentication auth = new UsernamePasswordAuthenticationToken(authToken, null);
return this.authenticationManager
.authenticate(auth)
.map(authentication -> (SecurityContext) new SecurityContextImpl(authentication))
ReactiveAuthenticationManager.class
#Slf4j
#Component
public class AuthenticationManager implements ReactiveAuthenticationManager {
final AuthenticationService authenticationService;
#Value("${app.auth_enable}")
private boolean isAuthEnabled;
public AuthenticationManager(AuthenticationService authenticationService) {
this.authenticationService = authenticationService;
}
#Override
public Mono<Authentication> authenticate(Authentication authentication) {
AuthToken token = (AuthToken) authentication.getPrincipal();
if (Objects.isNull(token)) {
log.error("Jwt token not provided");
return Mono.error(new AuthorizeException("Jwt token not provided"));
}
if (isAuthEnabled) {
return authenticationService
.verifyRequestToken(token)
.map(
aBoolean -> {
if (!aBoolean) {
log.warn("Jwt token not valid");
return null;
}
log.info("Jwt token is valid");
return new UsernamePasswordAuthenticationToken(token, null, null);
});
}
return Mono.just(new UsernamePasswordAuthenticationToken(token, null, null));
}
JwtAuthenticationEntryPoint.class
#Slf4j
#Component
public class JwtAuthenticationEntryPoint extends HttpBasicServerAuthenticationEntryPoint
implements Serializable {
private final ObjectMapper objectMapper;
public JwtAuthenticationEntryPoint(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
#Override
public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException ex) {
log.info("Commencing AuthenticationEntryPoint...");
ServerHttpResponse response = exchange.getResponse();
JwtAuthenticationError error =
new JwtAuthenticationError(JwtExceptionContext.getExceptionContext());
JwtExceptionContext.clearExceptionContext();
byte[] bytes = new byte[0];
try {
bytes = objectMapper.writeValueAsString(error).getBytes(StandardCharsets.UTF_8);
} catch (JsonProcessingException e) {
log.error("JsonProcessingException on commence function : {}", e.getMessage(), e);
}
DataBuffer buffer = response.bufferFactory().wrap(bytes);
response.setStatusCode(HttpStatus.valueOf(Integer.parseInt(error.getStatusCode())));
log.warn(
"Authentication Failed: {} -> {}",
value("errorMsg", error),
keyValue(
AppConstants.STATUS_CODE, HttpStatus.valueOf(Integer.parseInt(error.getStatusCode()))));
return response.writeWith(Mono.just(buffer));
}
Sample logs
2022-01-18 15:30:25.203 DEBUG 9308 --- [cTaskExecutor-1] o.s.web.client.RestTemplate : Reading to [java.lang.String] as "application/json"
2022-01-18 15:30:25.209 INFO 9308 --- [cTaskExecutor-1] c.s.p.r.n.h.s.AuthenticationServiceImpl : Validation Response 200
2022-01-18 15:30:25.209 INFO 9308 --- [oundedElastic-1] c.s.p.r.n.h.s.AuthenticationManager : **Jwt token is valid**
2022-01-18 15:30:25.211 DEBUG 9308 --- [oundedElastic-1] o.s.s.w.s.a.AuthorizationWebFilter : Authorization successful
2022-01-18 15:30:25.217 DEBUG 9308 --- [oundedElastic-1] s.w.r.r.m.a.RequestMappingHandlerMapping : [1fc32c7d-1, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:54225] Mapped to MyController#getCount(Boolean, String, UsernamePasswordAuthenticationToken)
2022-01-18 15:30:25.255 INFO 9308 --- [oundedElastic-1] c.s.p.r.n.h.s.AuthenticationServiceImpl : Validation Response 200
2022-01-18 15:30:25.256 INFO 9308 --- [oundedElastic-2] c.s.p.r.n.h.s.AuthenticationManager : **Jwt token is valid**
Any suggestions or pointers are appreciated.
Thanks in advance
I've allowed cors in my grails 3 application through :
cors:
enabled: true
and added the filter :
public CorsFilter() { }
#Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse resp, FilterChain chain)
throws ServletException, IOException {
String origin = req.getHeader("Origin");
boolean options = "OPTIONS".equals(req.getMethod());
if (options) {
if (origin == null) return;
resp.addHeader("Access-Control-Allow-Headers", "origin, authorization, accept, content-type, x-requested-with");
resp.addHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS");
resp.addHeader("Access-Control-Max-Age", "3600");
}
resp.addHeader("Access-Control-Allow-Origin", origin == null ? "*" : origin);
resp.addHeader("Access-Control-Allow-Credentials", "true");
if (!options) chain.doFilter(req, resp);
}
The problem is the request is responding correctly,
but if the request has a header 'Origin', the request returns 403
even though the response header is :
Access-Control-Allow-Credentials →true
Access-Control-Allow-Origin →http://localhost:4200
Cache-Control →no-store, no-cache, must-revalidate, max-age=0
Content-Length →0
Date →Sat, 25 Feb 2017 19:44:21 GMT
X-Application-Context →application:development
Any idea how to solve this ?
Thanks
The issue was with websocket, since my error was happening with the url containing /stomp/info
The solution was to add the following class
#Configuration
#EnableWebSocketMessageBroker
class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
void configureMessageBroker(MessageBrokerRegistry messageBrokerRegistry) {
messageBrokerRegistry.enableSimpleBroker "/queue", "/hmi"
messageBrokerRegistry.setApplicationDestinationPrefixes "/app"
}
#Override
void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
stompEndpointRegistry.addEndpoint("/stomp","/hmi","/hmi/status").setAllowedOrigins("*").withSockJS()
}
#Bean
GrailsSimpAnnotationMethodMessageHandler grailsSimpAnnotationMethodMessageHandler(
MessageChannel clientInboundChannel,
MessageChannel clientOutboundChannel,
SimpMessagingTemplate brokerMessagingTemplate
) {
def handler = new GrailsSimpAnnotationMethodMessageHandler(clientInboundChannel, clientOutboundChannel, brokerMessagingTemplate)
handler.destinationPrefixes = ["/app"]
return handler
}
}
and then add it to resources.groovy
beans = {
websocketConfig WebSocketConfig
}
I am trying spring security and spring oauth2 in my project and have separated my authorization server and resource server. I didn't want to share a token store between these two servers so I decided to use RemoteTokenServices and the check_token endpoint. Everything was fine except when I used an access token to query the resource server, I got "401 Unauthorized" error as follows:
2015-10-19 11:50:10.291 DEBUG 2590 --- [nio-8080-exec-1] o.s.web.client.RestTemplate : POST request for "http://localhost:9080/uaa/oauth/check_token/" resulted in 401 (Unauthorized); invoking error handler
2015-10-19 11:50:10.293 DEBUG 2590 --- [nio-8080-exec-1] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
2015-10-19 11:50:10.293 DEBUG 2590 --- [nio-8080-exec-1] o.s.web.filter.RequestContextFilter : Cleared thread-bound request context: org.apache.catalina.connector.RequestFacade#41f4867a
2015-10-19 11:50:10.297 ERROR 2590 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[jerseyServlet] : Servlet.service() for servlet [jerseyServlet] in context with path [] threw exception
org.springframework.web.client.HttpClientErrorException: 401 Unauthorized
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91)
The code for the Authorisation server:
#Configuration
#EnableAuthorizationServer
public class OAuthConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private DataSource dataSource;
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Bean
protected AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(dataSource);
}
#Bean
public DefaultAccessTokenConverter defaultAccessTokenConverter() {
return new DefaultAccessTokenConverter();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(this.tokenStore())
.authenticationManager(authenticationManager)
.accessTokenConverter(defaultAccessTokenConverter());
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
}
And the security configuration:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication();
// .withUser("John").roles("ADMIN").password("password")
// .and()
// .withUser("Mary").roles("BASIC").password("password");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").authenticated()
.and().httpBasic().realmName("OAuth Server");
http.csrf().disable();
}
}
The Resource Server is set up as follows:
#Configuration
#EnableResourceServer
public class ResourceConfiguration extends ResourceServerConfigurerAdapter {
private static String RESOURCE_ID = "xn-resource-id";
private TokenExtractor tokenExtractor = new BearerTokenExtractor();
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().anyRequest().authenticated();
}
#Bean
public AccessTokenConverter accessTokenConverter() {
return new DefaultAccessTokenConverter();
}
#Bean
public RemoteTokenServices remoteTokenServices(final #Value("${auth.server.url}") String checkTokenUrl,
final #Value("${auth.server.client_id}") String clientId,
final #Value("${auth.server.client_secret}") String clientSecret) {
final RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
remoteTokenServices.setCheckTokenEndpointUrl(checkTokenUrl);
remoteTokenServices.setClientId(clientId);
remoteTokenServices.setClientSecret(clientSecret);
remoteTokenServices.setAccessTokenConverter(accessTokenConverter());
return remoteTokenServices;
}
}
I tested the security settings with curl and used client_credentials grant type.
Does anyone help me figure out what's the issue with the above code?
Looks like you are using incorrect url. Try to repleace it with:
http://localhost:9080/uaa/oauth/check_token
(notice that url is not ended with /)
I have several Controllers in my Spring-Boot (1.1.4.RELEASE)/ Spring-Security application that I would like to run some integration tests on. However, I don't know how to make the request so that the authentication is handled.
Here is my test:
#ContextConfiguration(classes = OFAC, loader = SpringApplicationContextLoader)
#Transactional
#WebAppConfiguration
#IntegrationTest
class AdminControllerIntegrationTest extends Specification {
def adminUrl = "http://localhost:9001/admin"
#Autowired
private AdminController adminController;
def "test retrieving users from db table"() {
def model = Mock(Model)
RestTemplate restTemplate = new TestRestTemplate()
when:
def result = restTemplate.getForEntity(adminUrl, String.class, model)
then:
result != null
}
Here is my security configuration:
#Configuration
#EnableWebMvcSecurity
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers( "/" ).permitAll()
.antMatchers( "/resources/**" ).permitAll()
.antMatchers( "/css/**" ).permitAll()
.antMatchers( "/libs/**" ).permitAll();
http
.formLogin().failureUrl( "/login?error" )
.defaultSuccessUrl( "/" )
.loginPage( "/login" )
.permitAll()
.and()
.logout().logoutRequestMatcher( new AntPathRequestMatcher( "/logout" ) ).logoutSuccessUrl( "/" )
.permitAll();
http
.sessionManagement()
.maximumSessions( 1 )
.expiredUrl( "/login?expired" )
.maxSessionsPreventsLogin( true )
.and()
.sessionCreationPolicy( SessionCreationPolicy.IF_REQUIRED )
.invalidSessionUrl( "/" );
http
.authorizeRequests().anyRequest().authenticated();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder encoder = new BCryptPasswordEncoder();
auth.userDetailsService( customUserDetailsService ).passwordEncoder( encoder );
}
When I debug that code,the result I get is the login html. I believe this means the authentication failed (and since I haven't defined the user /password anywhere) and my request is being redirected to the login page.
I looked for a good way to run Integration Tests against like this but haven't found a good solution. If anyone has any examples on how to handle this, I hope you would help
I think to do that kind of test you have no choice but to POST to the login form and extract the session cookie, so you can send it with the request you actually need to test. Something like this:
private String loginAndGrabCookie() {
ResponseEntity<String> page = serverRunning.getForString("/sparklr2/login.jsp");
String cookie = page.getHeaders().getFirst("Set-Cookie");
Matcher matcher = Pattern.compile("(?s).*name=\"_csrf\".*?value=\"([^\"]+).*").matcher(page.getBody());
MultiValueMap<String, String> formData;
formData = new LinkedMultiValueMap<String, String>();
formData.add("j_username", "marissa");
formData.add("j_password", "koala");
if (matcher.matches()) {
formData.add("_csrf", matcher.group(1));
}
String location = "/sparklr2/login.do";
HttpHeaders headers = new HttpHeaders();
headers.set("Cookie", cookie);
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
ResponseEntity<Void> result = serverRunning.postForStatus(location, headers , formData);
assertEquals(HttpStatus.FOUND, result.getStatusCode());
cookie = result.getHeaders().getFirst("Set-Cookie");
assertNotNull("Expected cookie in " + result.getHeaders(), cookie);
return cookie;
}
(Taken from https://github.com/spring-projects/spring-security-oauth/blob/master/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/AuthorizationCodeProviderTests.java#L381.)