apache Shiro Login - spring

I am kind of new to Apache shiro and trying to use authcBasic for securing the webservice.
I need to create a webservice using which I can login by providing username and password which can utilize apache shiro's features.
Any guidance will highly be appreciated

I have created a minimal example application with Spring-Boot (because of the "spring" tag) and Shiro for you, which you can find here on GitHub. The example application is based on the "hello world" RESTful web service with Spring application from the Spring docs. I have added Shiro to it via these changes (GitHub commit):
Add the shiro-spring dependency to pom.xml:
</dependencies>
[...]
<!-- Apache Shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
Copy shiro.ini from the Shiro docs to resources:
# =============================================================================
# Tutorial INI configuration
#
# Usernames/passwords are based on the classic Mel Brooks' film "Spaceballs" :)
# =============================================================================
# -----------------------------------------------------------------------------
# Users and their (optional) assigned roles
# username = password, role1, role2, ..., roleN
# -----------------------------------------------------------------------------
[users]
root = secret, admin
guest = guest, guest
presidentskroob = 12345, president
darkhelmet = ludicrousspeed, darklord, schwartz
lonestarr = vespa, goodguy, schwartz
# -----------------------------------------------------------------------------
# Roles with assigned permissions
# roleName = perm1, perm2, ..., permN
# -----------------------------------------------------------------------------
[roles]
admin = *
schwartz = lightsaber:*
goodguy = winnebago:drive:eagle5
Configure ShiroFilter, SecurityManager with IniRealm, and Shiro annotations in Application.java (adapted from here):
#SpringBootApplication
public class Application {
[...]
#Bean(name = "shiroFilter")
public FilterRegistrationBean shiroFilter() throws Exception {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter((AbstractShiroFilter) getShiroFilterFactoryBean().getObject());
registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
return registration;
}
#Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager());
Map<String, String> filterChainDefinitionMap = shiroFilterFactoryBean.getFilterChainDefinitionMap();
filterChainDefinitionMap.put("/**", "authcBasic");
return shiroFilterFactoryBean;
}
#Bean(name = "securityManager")
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
dwsm.setRealm(getShiroIniRealm());
final DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
// disable session cookie
sessionManager.setSessionIdCookieEnabled(false);
dwsm.setSessionManager(sessionManager);
return dwsm;
}
#Bean(name = "shiroIniRealm")
#DependsOn("lifecycleBeanPostProcessor")
public IniRealm getShiroIniRealm() {
return new IniRealm("classpath:shiro.ini");
}
#Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
#Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
daap.setProxyTargetClass(true);
return daap;
}
#Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(securityManager());
return new AuthorizationAttributeSourceAdvisor();
}
}
Add #RequiresRoles annotation with parameter "admin" to GreetingController for testing purposes:
#RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
#RequestMapping("/greeting")
#RequiresRoles(value = {"admin"})
public Greeting greeting(#RequestParam(value="name", defaultValue="World") String name) {
return new Greeting(counter.incrementAndGet(),
String.format(template, name));
}
}
Use the following commands to check out and run the application:
git clone https://github.com/opncow/gs-rest-service.git
cd gs-rest-service/complete/
./mvnw spring-boot:run
Verify that Shiro is working (use HttpRequester or similar plugin to create the following requests):
User "root" (has "admin" role) with password "secret" (Base64 encoded username:password as value of the Authorization header)
GET http://localhost:8080/greeting
Authorization: Basic cm9vdDpzZWNyZXQ=
-- response --
200
Set-Cookie: rememberMe=deleteMe; Path=/; Max-Age=0; Expires=Thu, 11-May-2017 00:29:44 GMT
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 12 May 2017 00:29:44 GMT
{"id":1,"content":"Hello, World!"}
User "guest" with password "guest" (no "admin" role):
GET http://localhost:8080/greeting
Authorization: Basic Z3Vlc3Q6Z3Vlc3Q=
-- response --
500
Set-Cookie: rememberMe=deleteMe; Path=/; Max-Age=0; Expires=Thu, 11-May-2017 00:44:18 GMT rememberMe=deleteMe; Path=/; Max-Age=0; Expires=Thu, 11-May-2017 00:44:18 GMT
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 12 May 2017 00:44:18 GMT
Connection: close
{"timestamp":1494549858572,"status":500,"error":"Internal Server Error","exception":"org.apache.shiro.authz.UnauthorizedException","message":"Subject does not have role [admin]","path":"/greeting"}
As can be seen, in the second request, the user guest is authenticated, however not authorized to use the greeting resource because of lacking the "admin" role (which means that the annotation is working).
This is the most minimal example I could imagine. It uses Shiro's .ini configuration/realm for users, passwords, and roles. For a real world project you will likely have to use a more sophisticated realm implementation such as Shiro's JdbcRealm

Related

Rate limit of the Rest api using spring cloud gateway does not work

I tried to run, but I don’t understand that why don’t show an error when to occur rate limit to rest api.
GatewaySecureRateLimiterTest - I only observe the success of requests, but I do not see errors when limiting requests.
In addition, I would like to clarify for myself (I did not find it in the documentation):
I would like to clarify for myself (I didn't find this in the documentation):
how to change the error code to the address to which the response was sent that the resource is currently busy
is it possible to collect statistics and see which IP generates more requests than our endpoint can handle
the speed limit can only be configured using *. yml, or it can also be configured using Java, while ?
I would like to see it in tests. what is the restriction-it triggers and gets detailed information(for example, from which IP and how many requests and in what unit of time).
I also didn't fully understand what the meaning of these parameters is:
key determinant: "#{#userRemoteAddressResolver}"
reuse rate limiter.Top-up rate: 1
reuse rate limiter.Bandwidth: 1
For example, I would like to know what is the number of requests in these parameters, and what is the unit of time during which this number of requests should work ?
server:
port: ${PORT:8085}
logging.pattern.console: "%clr(%d{HH:mm:ss.SSS}){blue} %clr(---){faint} %clr([%15.15t]){yellow} %clr(:){red} %clr(%m){faint}%n"
spring:
application:
name: gateway-service
redis:
host: 192.168.99.100
port: 6379
output.ansi.enabled: ALWAYS
cloud:
gateway:
routes:
- id: account-service
uri: http://localhost:8085
predicates:
- Path=/account/**
filters:
- RewritePath=/account/(?<path>.*), /$\{path}
- name: RequestRateLimiter
args:
key-resolver: "#{#userKeyResolver}"
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
config-security
#Configuration
//#ConditionalOnProperty("rateLimiter.secure")
#EnableWebFluxSecurity
public class SecurityConfig {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange(exchanges ->
exchanges
.anyExchange()
.authenticated())
.httpBasic();
http.csrf().disable();
return http.build();
}
#Bean
public MapReactiveUserDetailsService users() {
UserDetails user1 = User.builder()
.username("user1")
.password("{noop}1234")
.roles("USER")
.build();
UserDetails user2 = User.builder()
.username("user2")
.password("{noop}1234")
.roles("USER")
.build();
UserDetails user3 = User.builder()
.username("user3")
.password("{noop}1234")
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user1, user2, user3);
}
}
config
#Configuration
public class GatewayConfig {
#Bean
#Primary
#ConditionalOnProperty("rateLimiter.non-secure")
KeyResolver userKeyResolver() {
return exchange -> Mono.just("1");
}
// #Bean
// #ConditionalOnProperty("rateLimiter.secure")
KeyResolver authUserKeyResolver() {
return exchange -> ReactiveSecurityContextHolder.getContext()
.map(securityContext -> securityContext.getAuthentication()
.getPrincipal()
.toString()
);
}
}
test
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,
properties = {"rateLimiter.non-secure=true"})
#RunWith(SpringRunner.class)
public class GatewayRateLimiterTest {
private static final Logger LOGGER =
LoggerFactory.getLogger(GatewayRateLimiterTest.class);
private Random random = new Random();
#Rule
public TestRule benchmarkRun = new BenchmarkRule();
private static final DockerImageName IMAGE_NAME_MOCK_SERVER =
DockerImageName.parse("jamesdbloom/mockserver:mockserver-5.11.2");
#ClassRule
public static MockServerContainer mockServer =
new MockServerContainer(IMAGE_NAME_MOCK_SERVER);
#ClassRule
public static GenericContainer redis =
new GenericContainer("redis:5.0.6")
.withExposedPorts(6379);
#Autowired
TestRestTemplate testRestTemplate;
#Test
#BenchmarkOptions(warmupRounds = 0, concurrency = 6, benchmarkRounds = 600)
public void testAccountService() {
String username = "user" + (random.nextInt(3) + 1);
HttpHeaders headers = createHttpHeaders(username,"1234");
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<Account> responseEntity =
testRestTemplate.exchange("/account/{id}",
HttpMethod.GET,
entity,
Account.class,
1);
LOGGER.info("Received: status->{}, payload->{}, remaining->{}",
responseEntity.getStatusCodeValue(),
responseEntity.getBody(),
responseEntity.getHeaders()
.get("X-RateLimit-Remaining"));
}
private HttpHeaders createHttpHeaders(String user, String password) {
String notEncoded = user + ":" + password;
String encodedAuth = Base64.getEncoder().encodeToString(notEncoded.getBytes());
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.add("Authorization", "Basic " + encodedAuth);
return headers;
}
}
repository
here

How to retrieve attributes and username sent by the CAS server with Spring Security

I have a spring boot application, which is MVC in nature. All page of this application are being authenticated by CAS SSO.
I have used "spring-security-cas" as described at https://www.baeldung.com/spring-security-cas-sso
Everything working fine as expected. However, I have one problem - that is, I cannot retrieve attributes
and username sent by the CAS server in the following #Bean. What need I do to retrieve all the attributes
and and username sent by the CAS server?
#Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider provider = new CasAuthenticationProvider();
provider.setServiceProperties(serviceProperties());
provider.setTicketValidator(ticketValidator());
provider.setUserDetailsService(
s -> new User("casuser", "Mellon", true, true, true, true,
AuthorityUtils.createAuthorityList("ROLE_ADMIN")));
provider.setKey("CAS_PROVIDER_LOCALHOST_9000");
return provider;
}
First you will need to configure the attributeRepository source and the attributes to be retrieved, in attributeRepository section in CAS server, like:
cas.authn.attributeRepository.jdbc[0].singleRow=false
cas.authn.attributeRepository.jdbc[0].sql=SELECT * FROM USERATTRS WHERE {0}
cas.authn.attributeRepository.jdbc[0].username=username
cas.authn.attributeRepository.jdbc[0].role=role
cas.authn.attributeRepository.jdbc[0].email=email
cas.authn.attributeRepository.jdbc[0].url=jdbc:hsqldb:hsql://localhost:9001/xdb
cas.authn.attributeRepository.jdbc[0].columnMappings.attrname=attrvalue
cas.authn.attributeRepository.defaultAttributesToRelease=username,email,role
Check this example from CAS blog.
Then you need to implement an AuthenticationUserDetailsService at the service to read attributes returned from CAS authentication, something like:
#Component
public class CasUserDetailService implements AuthenticationUserDetailsService {
#Override
public UserDetails loadUserDetails(Authentication authentication) throws UsernameNotFoundException {
CasAssertionAuthenticationToken casAssertionAuthenticationToken = (CasAssertionAuthenticationToken) authentication;
AttributePrincipal principal = casAssertionAuthenticationToken.getAssertion().getPrincipal();
Map attributes = principal.getAttributes();
String uname = (String) attributes.get("username");
String email = (String) attributes.get("email");
String role = (String) attributes.get("role");
String username = authentication.getName();
Collection<SimpleGrantedAuthority> collection = new ArrayList<SimpleGrantedAuthority>();
collection.add(new SimpleGrantedAuthority(role));
return new User(username, "", collection);
}
}
Then, adjust your authenticationProvider with provider.setAuthenticationUserDetailsService(casUserDetailService);

Spring Boot add additional attribute to WebClient request in ServerOAuth2AuthorizedClientExchangeFilterFunction

I am trying to implement the client_credentials grant to get a token in my spring boot resource server.
I am using Auth0 as an Authorization server. They seem to require an extra parameter in the request body to be added called audience.
I have tried to do the request through postman and it works. I am now trying to reproduce it within Spring. Here is the working postman request
curl -X POST \
https://XXX.auth0.com/oauth/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=client_credentials&audience=https%3A%2F%2Fxxxxx.auth0.com%2Fapi%2Fv2%2F&client_id=SOME_CLIENT_ID&client_secret=SOME_CLIENT_SECRET'
The problem I am facing is that i have no way to add the missing audience parameter to the token request.
I have a configuration defined in my application.yml
client:
provider:
auth0:
issuer-uri: https://XXXX.auth0.com//
registration:
auth0-client:
provider: auth0
client-id: Client
client-secret: Secret
authorization_grant_type: client_credentials
auth0:
client-id: Client
client-secret: Secret
I have the web client filter configured like this.
#Bean
WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations,
ServerOAuth2AuthorizedClientRepository authorizedClients) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2 = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
clientRegistrations, authorizedClients);
oauth2.setDefaultClientRegistrationId("auth0");
return WebClient.builder()
.filter(oauth2)
.build();
}
I am injecting the instance and trying to do a request to get the user by email
return this.webClient.get()
.uri(this.usersUrl + "/api/v2/users-by-email?email={email}", email)
.attributes(auth0ClientCredentials())
.retrieve()
.bodyToMono(User.class);
The way i understand it, the filter intercepts this userByEmail request and before it executes it it tries to execute the /oauth/token request to get JWT Bearer token which it can append to the first one and execute it.
Is there a way to add a parameter to the filter? It has been extremely difficult to step through it and figure out where exactly the parameters are being appended since its reactive and am quite new at this. Even some pointers to where to look would be helpful.
I was having the same problem where access token response and request for it wasn't following oAuth2 standards. Here's my code (it's in kotlin but should be understandable also for java devs) for spring boot version 2.3.6.RELEASE.
Gradle dependencies:
implementation(enforcedPlatform("org.springframework.boot:spring-boot-dependencies:${springBootVersion}"))
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
After adding them you have to firstly create your custom token request/response client which will implement ReactiveOAuth2AccessTokenResponseClient interface:
class CustomTokenResponseClient : ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
private val webClient = WebClient.builder().build()
override fun getTokenResponse(
authorizationGrantRequest: OAuth2ClientCredentialsGrantRequest
): Mono<OAuth2AccessTokenResponse> =
webClient.post()
.uri(authorizationGrantRequest.clientRegistration.providerDetails.tokenUri)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.bodyValue(CustomTokenRequest(
clientId = authorizationGrantRequest.clientRegistration.clientId,
clientSecret = authorizationGrantRequest.clientRegistration.clientSecret
))
.exchange()
.flatMap { it.bodyToMono<NotStandardTokenResponse>() }
.map { it.toOAuth2AccessTokenResponse() }
private fun NotStandardTokenResponse.toOAuth2AccessTokenResponse() = OAuth2AccessTokenResponse
.withToken(this.accessToken)
.refreshToken(this.refreshToken)
.expiresIn(convertExpirationDateToDuration(this.data.expires).toSeconds())
.tokenType(OAuth2AccessToken.TokenType.BEARER)
.build()
}
As you can see above, in this class you can adjust token request/response handling to your specific needs.
Note: authorizationGrantRequest param inside getTokenResponse method. Spring is passing here data from you application properties, so follow the standards when defining them, e.g. they may look like this:
spring:
security:
oauth2:
client:
registration:
name-for-oauth-integration:
authorization-grant-type: client_credentials
client-id: id
client-secret: secret
provider:
name-for-oauth-integration:
token-uri: https://oauth.com/token
The last step is to use your CustomTokenResponseClient inside oAuth2 configuration, it may look like this:
#Configuration
class CustomOAuth2Configuration {
#Bean
fun customOAuth2WebWebClient(clientRegistrations: ReactiveClientRegistrationRepository): WebClient {
val clientRegistryRepo = InMemoryReactiveClientRegistrationRepository(
clientRegistrations.findByRegistrationId("name-for-oauth-integration").block()
)
val clientService = InMemoryReactiveOAuth2AuthorizedClientService(clientRegistryRepo)
val authorizedClientManager =
AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistryRepo, clientService)
val authorizedClientProvider = ClientCredentialsReactiveOAuth2AuthorizedClientProvider()
authorizedClientProvider.setAccessTokenResponseClient(CustomTokenResponseClient())
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
val oauthFilter = ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
oauthFilter.setDefaultClientRegistrationId("name-for-oauth-integration")
return WebClient.builder()
.filter(oauthFilter)
.build()
}
}
Right now, this is possible, but not elegant.
Note that you can provide a custom ReactiveOAuth2AccessTokenResponseClient to ServerOAuth2AuthorizedClientExchangeFilterFunction.
You can create your own implementation of this - and thereby add any other parameters you need - by copying the contents of WebClientReactiveClientCredentialsTokenResponseClient.
That said, it would be better if there were a setter to make that more convenient. You can follow the corresponding issue in Spring Security's backlog.
Here is what i found out after further investigation. The code described in my question was never going to call the client_credentials and fit my use-case. I think (not 100% sure on this) it will be very useful in the future if i am trying to propagate the user submitted token around multiple services in a micro-service architecture. A chain of actions like this comes to mind:
User calls Service A -> Service A calls Service B -> Service B responds -> Service A responds back to user request.
And using the same token to begin with through the whole process.
My solution to my use-case:
What i did was create a new Filter class largely based on the original and implement a step before executing the request where i check if i have a JWT token stored that can be used for the Auth0 Management API. If i don't i build up the client_credentials grant request and get one, then attach this token as a bearer to the initial request and execute that one. I also added a small token in-memory caching mechanism so that if the token is valid any other requests at a later date will just use it. Here is my code.
Filter
public class Auth0ClientCredentialsGrantFilterFunction implements ExchangeFilterFunction {
private ReactiveClientRegistrationRepository clientRegistrationRepository;
/**
* Required by auth0 when requesting a client credentials token
*/
private String audience;
private String clientRegistrationId;
private Auth0InMemoryAccessTokenStore auth0InMemoryAccessTokenStore;
public Auth0ClientCredentialsGrantFilterFunction(ReactiveClientRegistrationRepository clientRegistrationRepository,
String clientRegistrationId,
String audience) {
this.clientRegistrationRepository = clientRegistrationRepository;
this.audience = audience;
this.clientRegistrationId = clientRegistrationId;
this.auth0InMemoryAccessTokenStore = new Auth0InMemoryAccessTokenStore();
}
public void setAuth0InMemoryAccessTokenStore(Auth0InMemoryAccessTokenStore auth0InMemoryAccessTokenStore) {
this.auth0InMemoryAccessTokenStore = auth0InMemoryAccessTokenStore;
}
#Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
return auth0ClientCredentialsToken(next)
.map(token -> bearer(request, token.getTokenValue()))
.flatMap(next::exchange)
.switchIfEmpty(next.exchange(request));
}
private Mono<OAuth2AccessToken> auth0ClientCredentialsToken(ExchangeFunction next) {
return Mono.defer(this::loadClientRegistration)
.map(clientRegistration -> new ClientCredentialsRequest(clientRegistration, audience))
.flatMap(request -> this.auth0InMemoryAccessTokenStore.retrieveToken()
.switchIfEmpty(refreshAuth0Token(request, next)));
}
private Mono<OAuth2AccessToken> refreshAuth0Token(ClientCredentialsRequest clientCredentialsRequest, ExchangeFunction next) {
ClientRegistration clientRegistration = clientCredentialsRequest.getClientRegistration();
String tokenUri = clientRegistration
.getProviderDetails().getTokenUri();
ClientRequest clientCredentialsTokenRequest = ClientRequest.create(HttpMethod.POST, URI.create(tokenUri))
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.body(clientCredentialsTokenBody(clientCredentialsRequest))
.build();
return next.exchange(clientCredentialsTokenRequest)
.flatMap(response -> response.body(oauth2AccessTokenResponse()))
.map(OAuth2AccessTokenResponse::getAccessToken)
.doOnNext(token -> this.auth0InMemoryAccessTokenStore.storeToken(token));
}
private static BodyInserters.FormInserter<String> clientCredentialsTokenBody(ClientCredentialsRequest clientCredentialsRequest) {
ClientRegistration clientRegistration = clientCredentialsRequest.getClientRegistration();
return BodyInserters
.fromFormData("grant_type", AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.with("client_id", clientRegistration.getClientId())
.with("client_secret", clientRegistration.getClientSecret())
.with("audience", clientCredentialsRequest.getAudience());
}
private Mono<ClientRegistration> loadClientRegistration() {
return Mono.just(clientRegistrationId)
.flatMap(r -> clientRegistrationRepository.findByRegistrationId(r));
}
private ClientRequest bearer(ClientRequest request, String token) {
return ClientRequest.from(request)
.headers(headers -> headers.setBearerAuth(token))
.build();
}
static class ClientCredentialsRequest {
private final ClientRegistration clientRegistration;
private final String audience;
public ClientCredentialsRequest(ClientRegistration clientRegistration, String audience) {
this.clientRegistration = clientRegistration;
this.audience = audience;
}
public ClientRegistration getClientRegistration() {
return clientRegistration;
}
public String getAudience() {
return audience;
}
}
}
Token Store
public class Auth0InMemoryAccessTokenStore implements ReactiveInMemoryAccessTokenStore {
private AtomicReference<OAuth2AccessToken> token = new AtomicReference<>();
private Clock clock = Clock.systemUTC();
private Duration accessTokenExpiresSkew = Duration.ofMinutes(1);
public Auth0InMemoryAccessTokenStore() {
}
#Override
public Mono<OAuth2AccessToken> retrieveToken() {
return Mono.justOrEmpty(token.get())
.filter(Objects::nonNull)
.filter(token -> token.getExpiresAt() != null)
.filter(token -> {
Instant now = this.clock.instant();
Instant expiresAt = token.getExpiresAt();
if (now.isBefore(expiresAt.minus(this.accessTokenExpiresSkew))) {
return true;
}
return false;
});
}
#Override
public Mono<Void> storeToken(OAuth2AccessToken token) {
this.token.set(token);
return Mono.empty();
}
}
Token Store Interface
public interface ReactiveInMemoryAccessTokenStore {
Mono<OAuth2AccessToken> retrieveToken();
Mono<Void> storeToken(OAuth2AccessToken token);
}
And finally defining the beans and using it.
#Bean
public Auth0ClientCredentialsGrantFilterFunction auth0FilterFunction(ReactiveClientRegistrationRepository clientRegistrations,
#Value("${auth0.client-registration-id}") String clientRegistrationId,
#Value("${auth0.audience}") String audience) {
return new Auth0ClientCredentialsGrantFilterFunction(clientRegistrations, clientRegistrationId, audience);
}
#Bean(name = "auth0-webclient")
WebClient webClient(Auth0ClientCredentialsGrantFilterFunction filter) {
return WebClient.builder()
.filter(filter)
.build();
}
There is a slight problem with the token store at this time as the client_credentials token request will be executed multiple on parallel requests that come at the same time, but i can live with that for the foreseeable future.
Your application.yml is missing one variable:
client-authentication-method: post
it should be like this:
spring:
security:
oauth2:
client:
provider:
auth0-client:
token-uri: https://XXXX.auth0.com//
registration:
auth0-client:
client-id: Client
client-secret: Secret
authorization_grant_type: client_credentials
client-authentication-method: post
Without it I was getting "invalid_client" response all the time.
Tested in spring-boot 2.7.2

In spring, how about if PathVariable contains RequestMapping value

If I want to create a basic controller with RequestMapping = "/{content}" to handle the general case. But for some specific contents, I want to create a concrete controller for this special case, and inherit from that basic controller.
For example:
#RequestMapping(value = "/{content}")
class ContentController {
public ContentController(#PathVariable String content) { ... }
}
#RequestMapping(value = "/specialContent")
class SpecialContentController extends ContentController {
public SpecialContentController() { super("specialContent"); }
// overwrite sth
....
}
Is this legal? Or some other better implementation?
#PathVariable should not be used in constructor.
You seem confused about how controllers in spring work.
A controller is a singleton which is created on application upstart and whose methods are invoked to handle incoming requests.
Because your controller isn't created for each request but created before any requests are handled you can't use path variables in the constructor - both because there's no information about it's value when the instance is created but also because you'll want it to reflect the current request being handled and since controllers can handle many multiple requests simultaneously you can't store it as a class attribute or multiple requests would interfere with each other.
To achieve what you want you should use methods and compose them, something like this:
#RestController
public class ContentController {
#GetMapping("/specialContent")
public Map<String, String> handleSpecialContent() {
Map<String, String> map = handleContent("specialContent");
map.put("special", "true");
return map;
}
#GetMapping("/{content}")
public Map<String, String> handleContent(#PathVariable String content) {
HashMap<String, String> map = new HashMap<>();
map.put("content", content);
return map;
}
}
Note the regular expression in {content:^(?!specialContent$).*$} to ensure that Spring never routes specialContent there. You can get an explanation of the regular expression here and toy around with it here.
You can see that it works if we put it to the test:
$ http localhost:8080/test
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Date: Thu, 01 Feb 2018 08:18:11 GMT
Transfer-Encoding: chunked
{
"content": "test"
}
$ http localhost:8080/specialContent
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Date: Thu, 01 Feb 2018 08:18:15 GMT
Transfer-Encoding: chunked
{
"content": "specialContent",
"special": "true"
}

Custom Header not added through apache cxf OutInterceptor with spring

I have been struggling all day to have a custom SOAP request using spring application context and apache cxf and spring.
My Interceptor class looks like below
public class HttpHeaderInterceptor extends AbstractPhaseInterceptor<Message> {
public HttpHeaderInterceptor() {
super(Phase.SETUP);
}
#Override
public void handleMessage(Message message) throws Fault {
Map<String, List<String>> ietHeaders = new HashMap<String,List<String>>();
List<String> headerItems = new LinkedList<>();
ietHeaders.put("CustomHeader", Arrays.<String>asList("myheader"));
message.put(Message.PROTOCOL_HEADERS, ietHeaders);
}
}
WHen I check with Charlesproxy it's just the normal request. I am sure I am doing something wrong. At debug time , I can step into handleMessage method but nothing changes. The rest of the code snipet is available on pastie.org
Can anyone point out the oversight?
Thanks
Change Interceptor to SoapPreProtocolOutInterceptor. For details refer link
Hence modify the class as below.
public class HttpHeaderInterceptor extends SoapPreProtocolOutInterceptor {
public void handleMessage(SoapMessage message) throws Fault {
Map<String, List<String>> ietHeaders = new HashMap<String, List<String>>();
List<String> headerItems = new LinkedList<String>();
headerItems.add("h1");
headerItems.add("h2");
headerItems.add("h3");
ietHeaders.put("CustomHeader", headerItems);
message.put(Message.PROTOCOL_HEADERS, ietHeaders);
}
}
Modify your cxf-bean.xml to include interceptor
<jaxws:outInterceptors>
<bean class="com.kp.swasthik.soap.interceptor.HttpHeaderInterceptor" />
</jaxws:outInterceptors>
The output would be as below.
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
CustomHeader: h1,h2,h3
Content-Type: text/xml;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 12 Aug 2014 11:17:57 GMT

Resources