AccessDecisionVoter Deprecated with Spring Security 6.x - spring

In Spring Boot 2.7.x , I used the RoleHierarchyVoter
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_ADMINISTRATOR > ROLE_USER > ROLE_GUEST");
return roleHierarchy;
}
#Bean
public RoleHierarchyVoter roleVoter() {
return new RoleHierarchyVoter(roleHierarchy());
}
In Spring Boot 3.x AccessDecisionVoter are deprecated - the use of AuthorizationManager is recommended. Is there any posibility to use an AuthorizationManager-Implementation to set a role hierarchy? The usage of
#Bean
AccessDecisionVoter hierarchyVoter() {
RoleHierarchy hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF > ROLE_USER" +
"ROLE_USER > ROLE_GUEST");
return new RoleHierarchyVoter(hierarchy);
}
did not work since I use the AuthorizationFilter.

Simply exposing a expressionHandler bean should do the job.
If you use method security (#EnableMethodSecurity) :
#Bean
public RoleHierarchyImpl roleHierarchy() {
final RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF > ROLE_USER > ROLE_GUEST");
return roleHierarchy;
}
#Bean
public DefaultMethodSecurityExpressionHandler expressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy());
return expressionHandler;
}
If you use web security (#EnableWebSecurity) :
#Bean
public RoleHierarchyImpl roleHierarchy() {
final RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF > ROLE_USER > ROLE_GUEST");
return roleHierarchy;
}
#Bean
public DefaultWebSecurityExpressionHandler expressionHandler() {
DefaultWebSecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy());
return expressionHandler;
}

First, you create and instance of AuthorizationManager:
var access = AuthorityAuthorizationManager.<RequestAuthorizationContext>hasRole("USER");
This instance will be used to authorize our endpoints. So in the hasRole method, provide the name of your role or roles you want to use to protect a specific endpoint.
Second, you define your role hierarchy:
var hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
In this case we are defining that ROLE_ADMIN is above ROLE_USER, so ROLE_ADMIN has the same role as an ROLE_USER.
Third, we set the RoleHierarcy instance in the AuthorizationManager instance:
access.setRoleHierarchy(hierarchy);
Finally, we set the AuthorizationManager instance in our route protection configuration. So all the code togheter would look like this:
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
var access = AuthorityAuthorizationManager.<RequestAuthorizationContext>hasRole("USER");
var hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
access.setRoleHierarchy(hierarchy);
http.authorizeHttpRequests(authorize -> authorize.anyRequest().access(access));
return http.build();
}

Related

Spring Boot SAML problems using reverse proxy

I have a problem with the following scenario: multiple back-end servers process SAML requests forwarded by a reverse-proxy.
I tried to configure the contextProvider as follow: https://docs.spring.io/spring-security-saml/docs/2.0.x/reference/htmlsingle/#configuration-load-balancing
This is my SAML configuration
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger log = LoggerFactory.getLogger(OsiApplication.class);
private static final String PROTOCOL = "https";
private static final String BINDING = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST";
#Value("${saml.metadata-file}")
private String metadataFile;
#Value("${saml.idp-selection-path}")
private String idpSelectionPath;
#Value("${saml.openmind-app-id}")
private String openmindAppId;
#Value("${saml.success-redirect}")
private String successRedirect;
#Value("${saml.failure-redirect}")
private String failureRedirect;
#Value("${saml.lb-server-name}")
private String lbServerName;
#Value("${saml.lb-scheme}")
private String lbScheme;
#Value("${saml.lb-context-path}")
private String lbContextPath;
#Value("${saml.lb-server-port}")
private int lbServerPort;
#Value("${saml.lb-include-port}")
private boolean lbIncludePort;
#Value("${saml.store-path}")
private String storePath;
#Value("${saml.store-pass}")
private String storePass;
#Value("${saml.store-default-key}")
private String storeDefaultKey;
#Value("${saml.secured}")
private boolean secured;
#Value("${application.admin-code}")
private String adminCode;
#Value("${application.user-code}")
private String userCode;
#Autowired
private SAMLUserDetailsServiceImpl samlUserDetailsServiceImpl;
private Timer backgroundTaskTimer;
private MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager;
#PostConstruct
public void init() {
this.backgroundTaskTimer = new Timer(true);
this.multiThreadedHttpConnectionManager = new MultiThreadedHttpConnectionManager();
}
#PreDestroy
public void destroy() {
this.backgroundTaskTimer.purge();
this.backgroundTaskTimer.cancel();
this.multiThreadedHttpConnectionManager.shutdown();
}
// Initialization of the velocity engine
// XML parser pool needed for OpenSAML parsing
#Bean(initMethod = "initialize")
public StaticBasicParserPool parserPool() {
return new StaticBasicParserPool();
}
#Bean(name = "parserPoolHolder")
public ParserPoolHolder parserPoolHolder() {
return new ParserPoolHolder();
}
// Bindings, encoders and decoders used for creating and parsing messages
#Bean
public HttpClient httpClient() {
return new HttpClient(this.multiThreadedHttpConnectionManager);
}
// SAML Authentication Provider responsible for validating of received SAML
// messages
#Bean
public SAMLAuthenticationProvider samlAuthenticationProvider() {
SAMLAuthenticationProvider samlAuthenticationProvider = new SAMLAuthenticationProvider();
samlAuthenticationProvider.setUserDetails(samlUserDetailsServiceImpl);
samlAuthenticationProvider.setForcePrincipalAsString(false);
return samlAuthenticationProvider;
}
// Provider of default SAML Context
#Bean
public SAMLContextProviderLB contextProvider() {
// This error may occur during SP-initiated SSO. A SAML authn request is sent to the IdP and a
// SAML response is returned. We check that the InResponseTo field in the SAML response matches
// the ID field of the authn request.
// If they don't match then we throw the error you see
// FIX This can be done by setting DisableInResponseToCheck to true in the <PartnerIdentityProvider> entry in your saml.config.
SAMLContextProviderLB sAMLContextProviderImpl = new SAMLContextProviderLB();
// configuration of reverse proxy of saml
sAMLContextProviderImpl.setServerName(lbServerName);
sAMLContextProviderImpl.setScheme(lbScheme);
sAMLContextProviderImpl.setContextPath(lbContextPath);
sAMLContextProviderImpl.setServerPort(lbServerPort);
sAMLContextProviderImpl.setIncludeServerPortInRequestURL(false);
/*EmptyStorageFactory emptyStorageFactory = new EmptyStorageFactory();
sAMLContextProviderImpl.setStorageFactory(emptyStorageFactory);*/
return sAMLContextProviderImpl;
}
// Initialization of OpenSAML library
#Bean
public static SAMLBootstrap sAMLBootstrap() {
return new SAMLBootstrap();
}
// Logger for SAML messages and events
#Bean
public SAMLDefaultLogger samlLogger() {
return new SAMLDefaultLogger();
}
// SAML 2.0 WebSSO Assertion Consumer
#Bean
public WebSSOProfileConsumer webSSOprofileConsumer() {
WebSSOProfileConsumerImpl webSSOProfileConsumer = new WebSSOProfileConsumerImpl();
webSSOProfileConsumer.setMaxAuthenticationAge(28800);
return webSSOProfileConsumer;
}
// SAML 2.0 Holder-of-Key WebSSO Assertion Consumer
#Bean
public WebSSOProfileConsumerHoKImpl hokWebSSOprofileConsumer() {
return new WebSSOProfileConsumerHoKImpl();
}
// SAML 2.0 Web SSO profile
#Bean
public WebSSOProfile webSSOprofile() {
return new WebSSOProfileImpl();
}
// SAML 2.0 Holder-of-Key Web SSO profile
#Bean
public WebSSOProfileConsumerHoKImpl hokWebSSOProfile() {
return new WebSSOProfileConsumerHoKImpl();
}
// SAML 2.0 ECP profile
#Bean
public WebSSOProfileECPImpl ecpprofile() {
return new WebSSOProfileECPImpl();
}
#Bean
public SingleLogoutProfile logoutprofile() {
return new SingleLogoutProfileImpl();
}
// Central storage of cryptographic keys
#Bean
public KeyManager keyManager() {
DefaultResourceLoader loader = new DefaultResourceLoader();
Resource storeFile = loader
.getResource(storePath);
String defaultKey = storeDefaultKey;
Map<String, String> passwords = new HashMap<>();
passwords.put(defaultKey, storePass);
return new JKSKeyManager(storeFile, storePass, passwords, defaultKey);
}
// Setup TLS Socket Factory
#Bean
public TLSProtocolConfigurer tlsProtocolConfigurer() {
return new TLSProtocolConfigurer();
}
#Bean
public ProtocolSocketFactory socketFactory() {
return new TLSProtocolSocketFactory(keyManager(), null, "default");
}
#Bean
public Protocol socketFactoryProtocol() {
return new Protocol(PROTOCOL, socketFactory(), 443);
}
#Bean
public MethodInvokingFactoryBean socketFactoryInitialization() {
MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
methodInvokingFactoryBean.setTargetClass(Protocol.class);
methodInvokingFactoryBean.setTargetMethod("registerProtocol");
Object[] args = { PROTOCOL, socketFactoryProtocol() };
methodInvokingFactoryBean.setArguments(args);
return methodInvokingFactoryBean;
}
#Bean
public WebSSOProfileOptions defaultWebSSOProfileOptions() {
WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();
webSSOProfileOptions.setIncludeScoping(false);
webSSOProfileOptions.setBinding(BINDING);
return webSSOProfileOptions;
}
// Entry point to initialize authentication, default values taken from
// properties file
#Bean
public SAMLEntryPoint samlEntryPoint() {
SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint();
samlEntryPoint.setDefaultProfileOptions(defaultWebSSOProfileOptions());
return samlEntryPoint;
}
// Setup advanced info about metadata
#Bean
public ExtendedMetadata extendedMetadata() {
ExtendedMetadata extendedMetadata = new ExtendedMetadata();
extendedMetadata.setIdpDiscoveryEnabled(false);
extendedMetadata.setSignMetadata(true);
extendedMetadata.setEcpEnabled(false);
return extendedMetadata;
}
// IDP Discovery Service
#Bean
public SAMLDiscovery samlIDPDiscovery() {
SAMLDiscovery idpDiscovery = new SAMLDiscovery();
idpDiscovery.setIdpSelectionPath(idpSelectionPath);
return idpDiscovery;
}
#Bean
#Qualifier("idp-ssocircle")
public ExtendedMetadataDelegate ssoCircleExtendedMetadataProvider()
throws MetadataProviderException {
File file = null;
String metadata = metadataFile;
DefaultResourceLoader loader = new DefaultResourceLoader();
try {
file = loader.getResource(metadata).getFile();
} catch (IOException e) {
log.error("IOException => {}", e);
}
FilesystemMetadataProvider filesystemMetadataProvider = new FilesystemMetadataProvider(file);
filesystemMetadataProvider.setRequireValidMetadata(true);
filesystemMetadataProvider.setParserPool(new BasicParserPool());
filesystemMetadataProvider.initialize();
//
ExtendedMetadata extendedMetadata = new ExtendedMetadata();
extendedMetadata.setIdpDiscoveryEnabled(false);
extendedMetadata.setSignMetadata(true);
ExtendedMetadataDelegate extendedMetadataDelegate = new ExtendedMetadataDelegate(filesystemMetadataProvider, extendedMetadata());
extendedMetadataDelegate.setMetadataTrustCheck(false);
extendedMetadataDelegate.setMetadataRequireSignature(false);
return extendedMetadataDelegate;
}
// IDP Metadata configuration - paths to metadata of IDPs in circle of trust
// is here
// Do no forget to call initalize method on providers
#Bean
#Qualifier("metadata")
public CachingMetadataManager metadata() throws MetadataProviderException {
List<MetadataProvider> providers = new ArrayList<>();
providers.add(ssoCircleExtendedMetadataProvider());
return new CachingMetadataManager(providers);
}
// Filter automatically generates default SP metadata
#Bean
public MetadataGenerator metadataGenerator() {
MetadataGenerator metadataGenerator = new MetadataGenerator();
metadataGenerator.setEntityId(openmindAppId);
metadataGenerator.setExtendedMetadata(extendedMetadata());
metadataGenerator.setIncludeDiscoveryExtension(false);
metadataGenerator.setKeyManager(keyManager());
metadataGenerator.setEntityBaseURL(successRedirect);
return metadataGenerator;
}
// The filter is waiting for connections on URL suffixed with filterSuffix
// and presents SP metadata there
#Bean
public MetadataDisplayFilter metadataDisplayFilter() {
return new MetadataDisplayFilter();
}
// Handler deciding where to redirect user after successful login
#Bean
public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() {
SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successRedirectHandler.setDefaultTargetUrl(successRedirect);
return successRedirectHandler;
}
// Handler deciding where to redirect user after failed login
#Bean
public SimpleUrlAuthenticationFailureHandler authenticationFailureHandler() {
SimpleUrlAuthenticationFailureHandler simpleUrlAuthenticationFailureHandler = new SimpleUrlAuthenticationFailureHandler();
simpleUrlAuthenticationFailureHandler.setDefaultFailureUrl(failureRedirect);
return simpleUrlAuthenticationFailureHandler;
}
#Bean
public SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter() throws Exception {
SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter = new SAMLWebSSOHoKProcessingFilter();
samlWebSSOHoKProcessingFilter.setAuthenticationSuccessHandler(successRedirectHandler());
samlWebSSOHoKProcessingFilter.setAuthenticationManager(authenticationManager());
samlWebSSOHoKProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
return samlWebSSOHoKProcessingFilter;
}
// Processing filter for WebSSO profile messages
#Bean
public SAMLProcessingFilter samlWebSSOProcessingFilter() throws Exception {
SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter();
samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManager());
samlWebSSOProcessingFilter.setAuthenticationSuccessHandler(successRedirectHandler());
samlWebSSOProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
return samlWebSSOProcessingFilter;
}
#Bean
public MetadataGeneratorFilter metadataGeneratorFilter() {
return new MetadataGeneratorFilter(metadataGenerator());
}
// Handler for successful logout
#Bean
public SimpleUrlLogoutSuccessHandler successLogoutHandler() {
SimpleUrlLogoutSuccessHandler successLogoutHandler = new SimpleUrlLogoutSuccessHandler();
successLogoutHandler.setDefaultTargetUrl(lbContextPath);
return successLogoutHandler;
}
// Logout handler terminating local session
#Bean
public SecurityContextLogoutHandler logoutHandler() {
SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
logoutHandler.setInvalidateHttpSession(true);
logoutHandler.setClearAuthentication(true);
return logoutHandler;
}
// Filter processing incoming logout messages
// First argument determines URL user will be redirected to after successful
// global logout
#Bean
public SAMLLogoutProcessingFilter samlLogoutProcessingFilter() {
return new SAMLLogoutProcessingFilter(successLogoutHandler(),
logoutHandler());
}
// Overrides default logout processing filter with the one processing SAML
// messages
#Bean
public SAMLLogoutFilter samlLogoutFilter() {
return new SAMLLogoutFilter(successLogoutHandler(),
new LogoutHandler[] { logoutHandler() },
new LogoutHandler[] { logoutHandler() });
}
// Bindings
private ArtifactResolutionProfile artifactResolutionProfile() {
final ArtifactResolutionProfileImpl artifactResolutionProfile = new ArtifactResolutionProfileImpl(httpClient());
artifactResolutionProfile.setProcessor(new SAMLProcessorImpl(soapBinding()));
return artifactResolutionProfile;
}
#Bean
public HTTPArtifactBinding artifactBinding(ParserPool parserPool, VelocityEngine velocityEngine) {
return new HTTPArtifactBinding(parserPool, velocityEngine, artifactResolutionProfile());
}
#Bean
public HTTPSOAP11Binding soapBinding() {
return new HTTPSOAP11Binding(parserPool());
}
#Bean
public HTTPPostBinding httpPostBinding() {
return new HTTPPostBinding(parserPool(), velocityEngine());
}
#Bean
public HTTPRedirectDeflateBinding httpRedirectDeflateBinding() {
return new HTTPRedirectDeflateBinding(parserPool());
}
#Bean
public HTTPSOAP11Binding httpSOAP11Binding() {
return new HTTPSOAP11Binding(parserPool());
}
#Bean
public HTTPPAOS11Binding httpPAOS11Binding() {
return new HTTPPAOS11Binding(parserPool());
}
// Processor
#Bean
public SAMLProcessorImpl processor() {
Collection<SAMLBinding> bindings = new ArrayList<>();
bindings.add(httpRedirectDeflateBinding());
bindings.add(httpPostBinding());
bindings.add(artifactBinding(parserPool(), velocityEngine()));
bindings.add(httpSOAP11Binding());
bindings.add(httpPAOS11Binding());
return new SAMLProcessorImpl(bindings);
}
#Bean
public VelocityEngine velocityEngine() {
return VelocityFactory.getEngine();
}
/**
* Define the security filter chain in order to support SSO Auth by using SAML 2.0
*
* #return Filter chain proxy
* #throws Exception
*/
#Bean
public FilterChainProxy samlFilter() throws Exception {
List<SecurityFilterChain> chains = new ArrayList<>();
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/login/**"),
samlEntryPoint()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/logout/**"),
samlLogoutFilter()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/metadata/**"),
metadataDisplayFilter()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSO/**"),
samlWebSSOProcessingFilter()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSOHoK/**"),
samlWebSSOHoKProcessingFilter()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"),
samlLogoutProcessingFilter()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/discovery/**"),
samlIDPDiscovery()));
return new FilterChainProxy(chains);
}
/**
* Returns the authentication manager currently used by Spring. It represents a bean definition with the aim allow wiring from other classes performing the Inversion of Control (IoC).
*
* #throws Exception
*/
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* Defines the web based security configuration.
*
* #param http
* It allows configuring web based security for specific http requests.
* #throws Exception
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
/*http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.NEVER);*/
http
.httpBasic()
.authenticationEntryPoint(samlEntryPoint());
http
.csrf()
.disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http
.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
.addFilterAfter(samlFilter(), BasicAuthenticationFilter.class);
http
.authorizeRequests()
.antMatchers("/error").permitAll()
.antMatchers("/saml/**").permitAll()
.anyRequest()
//.permitAll();
.authenticated();
http
.exceptionHandling().accessDeniedPage("/403");
http
.logout()
.logoutSuccessUrl("/");
}
/**
* Sets a custom authentication provider.
*
* #param auth
* SecurityBuilder used to create an AuthenticationManager.
* #throws Exception
*/
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(samlAuthenticationProvider());
}
and this is my configuration file
saml.metadata-file= metadata.xml
saml.idp-selection-path= /osi/saml/login?idp=IDP
saml.openmind-app-id= com:na:app:local:dev
saml.success-redirect= https://localhost:4200/context
saml.success= https://localhost:4200/#/files
saml.failure-redirect= https://localhost:4200/#/error
saml.lb-server-name= localhost:4200
saml.lb-scheme= https
saml.lb-context-path= /context
saml.lb-server-port= 4200
saml.lb-include-port= true
saml.store-path= classpath:localhost.jks
saml.store-pass= pass
saml.store-default-key= 1
saml.secured= true
While trying to login the authentification failes:
INFO o.s.s.saml.log.SAMLDefaultLogger - AuthNResponse;FAILURE;100.83.63.69;com:na:app:local:dev;IDP;;;org.opensaml.common.SAMLException: InResponseToField of the Response doesn't correspond to sent message a87hdee46ffejbd317h793f719g64h
adding a web.xml file to the project with the tag solved the issues
<?xml version="1.0"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<distributable />
<!-- ... -->
</web-app>

Customization of TokenEndpoint in Sprin OAuth2

I would like to provide a custom implmentation of the TokenEndpoint class in Spring framework.
Ive copied over the TokenEndpoint class of spring and have made my changes to the required places. But when the applications starts, I'm always getting the error
Caused by: java.lang.IllegalStateException: TokenGranter must be provided
I have provided an implementation for TokenGranter in my OAuthConfig, but spring is not picking up that
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.pathMapping("/oauth/token", "/oauth/token/v1")
.tokenServices(tokenServices())
.tokenGranter(tokenGranter())
.authenticationManager(authenticationManager).tokenStore(tokenStore())
.tokenEnhancer(tokenEnhancer()).accessTokenConverter(accessTokenConverter());
}
#Bean
#Primary
public TokenGranter tokenGranter() {
TokenGranter tokenGranter = null;
if (tokenGranter == null) {
tokenGranter = new TokenGranter() {
private CompositeTokenGranter delegate;
#Override
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
if (delegate == null) {
delegate = new CompositeTokenGranter(getDefaultTokenGranters());
}
return delegate.grant(grantType, tokenRequest);
}
};
}
return tokenGranter;
}
I even tried to provide this implementation, in my custom TokenEndpoint class.
For now, the implementation of custom TokenEndpoint is exactly the same as Spring's TokenEndpoint.
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
private List<TokenGranter> getDefaultTokenGranters() {
ClientDetailsService clientDetails = clientDetailsService();
AuthorizationServerTokenServices tokenServices = tokenServices();
AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices();
OAuth2RequestFactory requestFactory = requestFactory();
List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();
tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails,
requestFactory));
tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));
ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory);
tokenGranters.add(implicit);
tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory));
if (authenticationManager != null) {
tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetails,
requestFactory));
}
return tokenGranters;
}
private DefaultTokenServices createDefaultTokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore());
tokenServices.setSupportRefreshToken(true);
tokenServices.setReuseRefreshToken(true);
tokenServices.setClientDetailsService(clientDetailsService());
tokenServices.setTokenEnhancer(tokenEnhancer());
addUserDetailsService(tokenServices, new CustomDetailsService());
return tokenServices;
}
private ClientDetailsService clientDetailsService() {
ClientDetailsService clientDetailsService = null;
clientDetailsService = new InMemoryClientDetailsService();
addUserDetailsService(createDefaultTokenServices(), new CustomDetailsService());
return clientDetailsService;
}
private void addUserDetailsService(DefaultTokenServices tokenServices, UserDetailsService userDetailsService) {
if (userDetailsService != null) {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>(
userDetailsService));
tokenServices
.setAuthenticationManager(new ProviderManager(Arrays.<AuthenticationProvider> asList(provider)));
}
}
private AuthorizationCodeServices authorizationCodeServices() {
AuthorizationCodeServices authorizationCodeServices = new InMemoryAuthorizationCodeServices();
return authorizationCodeServices;
}
private OAuth2RequestFactory requestFactory() {
OAuth2RequestFactory requestFactory = new DefaultOAuth2RequestFactory(clientDetailsService());
return requestFactory;
}
#Bean
public JwtTokenStore tokenStore() {
JwtTokenStore jwtTokenStore = new JwtTokenStore(accessTokenConverter());
return jwtTokenStore;
}
#Bean
#Primary
public AuthorizationServerTokenServices tokenServices() {
final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setAccessTokenValiditySeconds(-1);
defaultTokenServices.setTokenStore(tokenStore());
return defaultTokenServices;
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
final JwtAccessTokenConverter converter = new JwtAccessTokenConverter() {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
return accessToken;
}
};
return converter;
}
Ive been trying to figure this out for a couple of days, but without any luck. So any help would be much appreciated.
I know the question is quite old, but I encountered the same problem and didn't manage to find a complete guide on customizing TokenEndpoint. I wasn't be able to use TokenEnhancer, because I needed to change headers of the response. So, this is the version worked for me.
You define your overwritten controller as usual:
#RequestMapping(value = "/oauth/token")
public class CustomTokenEndpoint extends TokenEndpoint {
#PostMapping
public ResponseEntity<OAuth2AccessToken> postAccessToken(
Principal principal,
#RequestParam Map<String, String> parameters
) throws HttpRequestMethodNotSupportedException {
ResponseEntity<OAuth2AccessToken> defaultResponse = super.postAccessToken(principal, parameters);
// do some work
return defaultResponse;
}
}
And you need to create your own TokenEndpoint bean:
#Bean
#Primary
public TokenEndpoint tokenEndpoint(AuthorizationServerEndpointsConfiguration conf) {
TokenEndpoint tokenEndpoint = new CustomTokenEndpoint();
tokenEndpoint.setClientDetailsService(conf.getEndpointsConfigurer().getClientDetailsService());
tokenEndpoint.setProviderExceptionHandler(conf.getEndpointsConfigurer().getExceptionTranslator());
tokenEndpoint.setTokenGranter(conf.getEndpointsConfigurer().getTokenGranter());
tokenEndpoint.setOAuth2RequestFactory(conf.getEndpointsConfigurer().getOAuth2RequestFactory());
tokenEndpoint.setOAuth2RequestValidator(conf.getEndpointsConfigurer().getOAuth2RequestValidator());
tokenEndpoint.setAllowedRequestMethods(conf.getEndpointsConfigurer().getAllowedTokenEndpointRequestMethods());
return tokenEndpoint;
}
And here's the kicker. You need to allow overwriting spring beans in your application.properties:
spring.main.allow-bean-definition-overriding: true
Hope this helps someone
Why do you need to implement TokenEndpoint again?
You can create a TokenGranter bean and inject it to default endpoints.
Where is getDefaultTokenGranters() method?
It looks like you have an incomplete copy of AuthorizationServerEndpointsConfigurer source code.
Update:
If you want to customize the token response ,use TokenEnhancer.
for example:
public class CustomTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
OurUser user = (OurUser) authentication.getPrincipal();
final Map<String, Object> additionalInfo = new HashMap<>();
Map<String, Object> userDetails = new HashMap<>();
userDetails.put(USERID, user.getId().getId());
userDetails.put(NAME, user.getName());
userDetails.put(MOBILE, user.getMobile());
userDetails.put(EMAIL, user.getEmail());
additionalInfo.put(USERINFO, userDetails);
// Set additional information in token for retriving in #org.springframework.security.oauth2.provider.endpoint.CheckTokenEndpoint
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
in OAuth2 Config:
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
super.configure(endpoints);
endpoints.
.....
// Include additional information to OAuth2 Access token with custom token enhancer
.tokenEnhancer(tokenEnhancer());
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
https://stackoverflow.com/a/28512607/4377110

Why the "/index" redirect to the UnauthorizedUrl in shiro?

I test the shiro with springboot,but no matter what urls like 127.0.0.1:8080/index are redirected to the the UnauthorizedUrl("/error");
Here is my ShiroConfig:
#Configuration
public class ShiroConfig {
#Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
#Bean
#DependsOn("lifecycleBeanPostProcessor")
public MyShiroRealm myShiroRealm(){
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
#Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
hashedCredentialsMatcher.setHashIterations(2);
return hashedCredentialsMatcher;
}
#Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
//securityManager.setCacheManager(ehCacheManager());
return securityManager;
}
#Bean
public ShiroFilterFactoryBean shiroFilter() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager());
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
filterChainDefinitionMap.put("/index", "anon");
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/welcome");
//Unauthorized;
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
#Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
#Bean
#ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}
#Bean
public PassThruAuthenticationFilter passThruAuthenticationFilter(){
return new PassThruAuthenticationFilter();
}
}
And the Realm
public class MyShiroRealm extends AuthorizingRealm {
private static final Logger LOGGER = Logger.getLogger(MyShiroRealm.class);
#Resource
UserService userService;
#Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
LOGGER.info("AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) : "+principalCollection);
String principal=(String) principalCollection.getPrimaryPrincipal();
LOGGER.info(principal);
User user=(User) userService.findUserByName(principal);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
for(Role role:user.getRoles()){
info.addRole(role.getName());
for(Permission permission:role.getPermissions()){
info.addStringPermission(permission.getName());
}
}
return info;
}
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
LOGGER.info("AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) : "+authenticationToken);
String name=((UsernamePasswordToken)authenticationToken).getUsername();
User user=userService.findUserByName(name);
if (user != null) {
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute("user", user);
return new SimpleAuthenticationInfo(name,user.getPassword(),getName());
} else {
return null;
}
}
}
Properties in application.properties
#thymeleaf
spring.thymeleaf.cache=false
#hibernate
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql= true
#html
spring.mvc.view.prefix=/
spring.mvc.view.suffix=.html
DB properties are not given. Tomcat's server port is 8080.
The recources dir:
dir of resource
When I access the "127.0.0.1:8080/index" ,it redirects to the "error" page setted by
shiroFilterFactoryBean.setUnauthorizedUrl("/error");.
when the "/login" Controller method is given it redirects to login page.
I am so confused and cannot find a way to solve it.
UPDATE
After moving the index.html to templates dir and make a #RequestMapping("/index") method,I get the index in browser.(If the index.html is in the static dir, A #RequestMapping("/index") method don't work.)
How could I get the static html ?
As I test, for the resources in static folder like "/css/**","/index.html" ,
If we want to access them without being intercepted by shiro,
full name like
"127.0.0.1:8080/css/a.css" or "127.0.0.1:8080/index.html",
should be used,
which the filterChainDefinitionMap should be setted as
filterChainDefinitionMap.put("/index.html", "anon");
filterChainDefinitionMap.put("/css/**", "anon");

Multiple LDAP repositories with Spring LDAP Repository

I would like to set more than one LDAP repositories with Spring LDAP. My aim is to create or update objects in all repositories at the same time.
I use LdapRepository Spring interface and I think that isn't possible for now.
I wonder if I can create my own LdapRepository extending the Spring one but I have no idea how to start.
This my configuration :
#Configuration
#EnableLdapRepositories("com.xxx.repository.ldap")
#PropertySource("classpath:ldap.properties")
public class LdapConfiguration {
#Autowired
Environment ldapProperties;
#Bean
public LdapContextSourceCustom contextSourceTarget() {
LdapContextSourceCustom ldapContextSource = new LdapContextSourceCustom();
ldapContextSource.setUrl(ldapProperties.getProperty("ldap.url"));
ldapContextSource.setBase(ldapProperties.getProperty("ldap.base"));
ldapContextSource.setUserDn(ldapProperties.getProperty("ldap.userDn"));
ldapContextSource.setPassword(ldapProperties.getProperty("ldap.password"));
ldapContextSource.setKeyStoreFile(ldapProperties.getProperty("ldap.truststore"));
return ldapContextSource;
}
#Bean
public LdapTemplate ldapTemplate(){
return new LdapTemplate(contextSourceTarget());
}
}
And to be complete, one repository:
public interface LdapUserRepository extends LdapRepository<LdapUser> {
}
Any idea how to do it ?
Thanks in advance for any help.
1) It is possible specify more than one LDAP Repository configuration. Please see the following example. [Notice: This depends on spring-boot libraries]
#Configuration
#EnableLdapRepositories("com.xxx.repository.ldap")
#EnableConfigurationProperties(LdapProperties.class)
public class LdapConfiguration {
#Autowired
private Environment environment;
#Bean(name="contextSource1")
public LdapContextSource contextSourceTarget(LdapProperties ldapProperties) {
LdapContextSource source = new LdapContextSource();
source.setUserDn(this.properties.getUsername());
source.setPassword(this.properties.getPassword());
source.setBase(this.properties.getBase());
source.setUrls(this.properties.determineUrls(this.environment));
source.setBaseEnvironmentProperties(Collections.<String,Object>unmodifiableMap(this.properties.getBaseEnvironment()));
return source;
}
#Bean
public LdapTemplate ldapTemplate(#Qualifier("contextSource1") LdapContextSource contextSource){
return new LdapTemplate(contextSource);
}
}
You can use the spring.ldap prefix in application.properties to configure the above LdapConfiguration. You can see the available properties by checking out https://github.com/spring-projects/spring-boot/blob/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ldap/LdapProperties.java.
#Configuration
#EnableLdapRepositories(basePackages="com.yyy.repository.ldap", ldapTemplateRef="ldapTemplate2")
public class LdapConfiguration2 {
#Autowired
private Environment environment;
#Bean(name="ldapProperties2")
#ConfigurationProperties(prefix="spring.ldap2")
public LdapProperties ldapProperties() {
return new LdapProperties();
}
#Bean(name="contextSource2")
public LdapContextSource contextSourceTarget(#Qualifier("ldapProperties2") LdapProperties ldapProperties) {
LdapContextSource source = new LdapContextSource();
source.setUserDn(this.properties.getUsername());
source.setPassword(this.properties.getPassword());
source.setBase(this.properties.getBase());
source.setUrls(this.properties.determineUrls(this.environment));
source.setBaseEnvironmentProperties(Collections.<String,Object>unmodifiableMap(this.properties.getBaseEnvironment()));
return source;
}
#Bean(name="ldapTemplate2")
public LdapTemplate ldapTemplate(#Qualifier("contextSource2") LdapContextSource contextSource){
return new LdapTemplate(contextSource);
}
}
LdapConfiguration2 will be configured by the spring.ldap2 prefix in application.properties.
2) I don't think extending the Repository is the solution. I would recommend creating a #Service method that iterated through your repositories and applied the updates. I will provide two approaches below.
Example 1)
#Service
public class UpdateRepositories {
public void updateAllRepositories(LdapUserRepository userRepository1, LdapUserRepository userRepository2) {
// apply updates to userRepository1 and userRepository2
}
}
Example 2)
#Service
public class UpdateRepositories {
public void updateAllRepositories(ApplicationContext appContext) {
Map<String, LdapRepository> ldapRepositories = appContext.getBeansofType(LdapRepository.class)
// iterate through map and apply updates
}
}
I haven't compiled this code, so let me know if something is off or if you need additional guidance.
I don't known if I understood correctly but here is what we did:
Global configuration class
#Bean("odm")
public ObjectDirectoryMapper odm() {
return new DefaultObjectDirectoryMapper();
};
First LDAP configuration class
#Configuration
#PropertySource("classpath:ldap-one.properties")
public class LdapOneConfiguration {
#Autowired
Environment ldapProperties;
#Bean(name = "contextSourceOne")
public LdapContextSourceCustom contextSourceLdapOneTarget() {
LdapContextSourceCustom ldapContextSource = new LdapContextSourceCustom();
ldapContextSource.setUrl(ldapProperties.getProperty("ldap-one.url"));
ldapContextSource.setBase(ldapProperties.getProperty("ldap-one.base"));
ldapContextSource.setUserDn(ldapProperties.getProperty("ldap-one.userDn"));
ldapContextSource.setPassword(ldapProperties.getProperty("ldap-one.password"));
ldapContextSource.setKeyStoreFile(ldapProperties.getProperty("ldap-one.truststore"));
return ldapContextSource;
}
#Bean(name = "ldapTemplateOne")
public LdapTemplate ldapOneTemplate(#Qualifier("contextSourceOne") LdapContextSourceCustom contextSource) {
return new LdapTemplate(contextSource);
}
#Bean(name = "ldapUserRepoOne")
public LdapUserRepository ldapUserRepositoryOne(#Qualifier("ldapTemplateOne") LdapTemplate ldapTemplate,
#Qualifier("odm") ObjectDirectoryMapper odm) {
return new LdapUserRepository(ldapTemplate, odm);
}
#Bean(name = "ldapFamilyRepoOne")
public LdapFamilyRepository ldapFamilyRepositoryOne(#Qualifier("ldapTemplateOne") LdapTemplate ldapTemplate,
#Qualifier("odm") ObjectDirectoryMapper odm) {
return new LdapFamilyRepository(ldapTemplate, odm);
}
}
Second LDAP configuration class
#Configuration
#PropertySource("classpath:ldap-two.properties")
public class LdapTwoConfiguration {
#Autowired
Environment ldapProperties;
#Bean(name = "contextSourceTwo")
public LdapContextSourceCustom contextSourceLdapTwoTarget() {
LdapContextSourceCustom ldapContextSource = new LdapContextSourceCustom();
ldapContextSource.setUrl(ldapProperties.getProperty("ldap-two.url"));
ldapContextSource.setBase(ldapProperties.getProperty("ldap-two.base"));
ldapContextSource.setUserDn(ldapProperties.getProperty("ldap-two.userDn"));
ldapContextSource.setPassword(ldapProperties.getProperty("ldap-two.password"));
ldapContextSource.setKeyStoreFile(ldapProperties.getProperty("ldap-two.truststore"));
return ldapContextSource;
}
#Bean(name = "ldapTemplateTwo")
public LdapTemplate ldapTwoTemplate(#Qualifier("contextSourceTwo") LdapContextSourceCustom contextSource) {
return new LdapTemplate(contextSource);
}
#Bean(name = "ldapUserRepoTwo")
public LdapUserRepository ldapUserRepositoryTwo(#Qualifier("ldapTemplateTwo") LdapTemplate ldapTemplate,
#Qualifier("odm") ObjectDirectoryMapper odm) {
return new LdapUserRepository(ldapTemplate, odm);
}
#Bean(name = "ldapFamilyRepoTwo")
public LdapFamilyRepository ldapFamilyRepositoryTwo(#Qualifier("ldapTemplateTwo") LdapTemplate ldapTemplate,
#Qualifier("odm") ObjectDirectoryMapper odm) {
return new LdapFamilyRepository(ldapTemplate, odm);
}
}
LdapUser repository
public class LdapUserRepository extends SimpleLdapRepository<LdapUser> {
public LdapUserRepository(LdapOperations ldapOperations, ObjectDirectoryMapper odm) {
super(ldapOperations, odm, LdapUser.class);
}
}
LdapFamily repository
public class LdapFamilyRepository extends SimpleLdapRepository<LdapFamily> {
public LdapFamilyRepository(LdapOperations ldapOperations, ObjectDirectoryMapper odm) {
super(ldapOperations, odm, LdapFamily.class);
}
}
LdapUser service (same for LdapFamily service)
#Service
public class LdapUserServiceImpl implements LdapUserService {
#Autowired
private ApplicationContext appContext;
private LdapUserRepository uniqueLdapUserRepo;
private List<LdapUserRepository> ldapUserRepoList;
#PostConstruct
private void setUniqueRepo() {
uniqueLdapUserRepo = appContext.getBeansOfType(LdapUserRepository.class).values().iterator().next();
ldapUserRepoList = new ArrayList<>(appContext.getBeansOfType(LdapUserRepository.class).values());
}
#Override
public LdapUser getUser(String uid) {
return uniqueLdapUserRepo.findOne(query().where("uid").is(uid));
}
#Override
public void saveUser(LdapUser user) {
for(LdapUserRepository repo: ldapUserRepoList){
repo.save(user);
}
}
}
We deleted the auto configuration of LDAP repo:
#EnableLdapRepositories(basePackages = "com.afklm.paul.repository.ldap", ldapTemplateRef = "ldapTwoTemplate")
Thanks ryan2049 for your help.
there is actually an easier way now:
create multiple configuration that is anotated with #EnableLdapRepositories with corresponding attributes
Create first configuration
#Configuration
#EnableLdapRepositories(basePackages = "first.ldap.package.repository.**", ldapTemplateRef = "firstLdapTemplate")
public class FirstLDAPConfig {
....detail
#Bean("firstLdapTemplate")
public LdapTemplate firstLdapTemplate() {
...template creation
}
}
Create second configuration
#Configuration
#EnableLdapRepositories(basePackages = "second.ldap.package.repository.**", ldapTemplateRef = "secondLdapTemplate")
public class SecondLDAPConfig {
....detail
#Bean("secondLdapTemplate")
public LdapTemplate secondLdapTemplate() {
...template creation
}
}
each configuration should handle it's own contextSource
then only the specified repository within the EnableLdapRepositories annotation will use that specific ContextSource and LdapTemplate

method level security using user roles in spring security oauth2

How can i have method level security in resource server using spring security oauth2? I am aware of client scopes and roles, I am talking about method level security using user roles something like -
#PreAuthorize("hasRole('ROLE_ADMIN')")
#RequestMapping("/accessibleByAdminOnly")
public String accessibleByAdminOnly() {
return "Welcome Admin!";
}
My resource server config looks like this -
#SpringBootApplication
#Configuration
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class DemoAPIServiceApp extends GlobalMethodSecurityConfiguration {
#Autowired
private ApplicationContext applicationContext;
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler = new DefaultMethodSecurityExpressionHandler();
methodSecurityExpressionHandler.setRoleHierarchy(roleHierarchy());
methodSecurityExpressionHandler.setApplicationContext(applicationContext);
return methodSecurityExpressionHandler;
}
#Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
return roleHierarchy;
}
#Bean
public RoleVoter roleVoter() {
return new RoleHierarchyVoter(roleHierarchy());
}
public static void main(String[] args) {
SpringApplication.run(DemoAPIServiceApp.class, args);
}
}
I am using a standard spring oauth2 auth server configuration with custom user details. I have one client and one scope.
Please help. The method level security is not working with this configuration.

Resources