I'm using Spring Security 5.1.2 in Restful Spring MVC project. I have used Custom Filter, Authentication Provider etc. In my Project which works fine, My Security config file is as follows:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationProcessingFilter jwtAuthenticationProcessingFilter;
#Autowired
private JwtAuthenticationProvider jwtAuthenticationProvider;
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private JwtAccessDeniedHandler jwtAccessDeniedHandler;
#Autowired
private RolePermissionService rolePermissionService;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(this.jwtAuthenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers(AuthenticationPatternType.SKIP_PATTERN.getPattern()).permitAll();
try {
List<PermissionRule> permissionRules = this.rolePermissionService.permissionRules();
for (PermissionRule p : permissionRules)
http.authorizeRequests().antMatchers(p.getPermission()).hasAnyRole(p.getRoles().toArray(new String[0]));
} catch (SystemException ignored) {
}
http.authorizeRequests().antMatchers(AuthenticationPatternType.AUTH_PATTERN.getPattern()).authenticated();
this.jwtAuthenticationProcessingFilter.init(authenticationManagerBean());
http.addFilterBefore(this.jwtAuthenticationProcessingFilter, UsernamePasswordAuthenticationFilter.class);
http.exceptionHandling()
.authenticationEntryPoint(this.jwtAuthenticationEntryPoint)
.accessDeniedHandler(this.jwtAccessDeniedHandler);
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.csrf().disable();
}
}
And my RolePermissionService Class is as follows:
#Service
public class RolePermissionService {
#Autowired
private PermissionDao permissionDao;
public List<PermissionRule> permissionRules() {
List<PermissionEntity> permissionEntities = this.permissionDao.list();
return permissionEntities.stream().map(PermissionRule::new)
.collect(Collectors.toList());
}
}
public class PermissionRule {
private String permission;
private List<String> roles;
public PermissionRule(PermissionEntity permissionEntity) {
this.permission = permissionEntity.getUrl();
this.roles = permissionEntity.getRoles().stream().map(RoleEntity::getName)
.collect(Collectors.toList());
}
// getters and setters
}
In this project, I have role(rid, name), permission(pid, url) and rolePermission(pid, rid) tables which holds my RBAC data. As you can see I'm reading this data from database (RolePermissionService Class) and load this data to HttpSecurity object in above config file.
Now, assume there is a rest Api which I want to edit rolePermission table or assume there is a rest Api which I want to add a role and its permissions in run time.
Q: How can I update security configuration which I can edit RBAC dynamically at run time?
Please refer https://github.com/liubo-tech/spring-security-rbac
#PreAuthorize("hasAuthority(T(com.example.springsecurityrbac.config.PermissionContact).USER_VIEW)")
Used to annotate method for securing. Uses database table mapping to assign permissions.
Please refer repo for further information
Related
Given is a Spring Boot application with a custom ProviderManager:
#Component
public class CustomProviderManager extends ProviderManager {
public CustomProviderManager(
AuthenticationProvider internalAuthenticationProvider,
AuthenticationProvider devUserAuthenticationProvider) {
super(internalAuthenticationProvider, devUserAuthenticationProvider);
}
}
The SecurityFilterChain is setup with a custom UsernamePasswordAuthenticationFilter:
#Bean
public SecurityFilterChain mvcFilterChain(HttpSecurity http) throws Exception {
return http
//....
.addFilterAt(internalUsernamePasswordAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
//....
}
And here the custom UsernamePasswordAuthenticationFilter:
#Component
public class InternalUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final SecurityContextRepository securityContextRepository;
private final AuthenticationFailureHandler authenticationFailureHandler;
private final AuthenticationSuccessHandler authenticationSuccessHandler;
#PostConstruct
private void setup() {
super.setUsernameParameter("identifier");
super.setPasswordParameter("password");
super.setFilterProcessesUrl("/authenticate");
super.setSecurityContextRepository(securityContextRepository);
super.setAuthenticationFailureHandler(authenticationFailureHandler);
super.setAuthenticationSuccessHandler(authenticationSuccessHandler);
super.afterPropertiesSet();
}
public InternalUsernamePasswordAuthenticationFilter(
AuthenticationManager customProviderManager,
SecurityContextRepository delegatingSecurityContextRepository,
AuthenticationFailureHandler authenticationFailureHandler,
AuthenticationSuccessHandler authenticationSuccessHandler) {
this.securityContextRepository = delegatingSecurityContextRepository;
this.authenticationFailureHandler = authenticationFailureHandler;
this.authenticationSuccessHandler = authenticationSuccessHandler;
super.setAuthenticationManager(customProviderManager);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
//....
}
}
What I want to do now is testing the authentication logic. But instead of using the authentication providers of the application, I want to use a special UserDetailsManager for testing only. The current TestConfiguration class containing a TestUserDetailsManager looks like that:
#TestConfiguration
public class TestUserDetailsManagerConfig {
#Bean
#Primary
public UserDetailsManager testUserDetailsManager() {
User.UserBuilder users = User.builder();
UserDetails testUser = users
.username("test-user#example.com")
.password("test-user")
.roles("USER")
.build();
UserDetails testAdmin = users
.username("test-admin#example.com")
.password("test-admin")
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(testUser, testAdmin);
}
}
And finally, a test method that should authenticate against the TestUserDetailsManager:
#SpringBootTest
#Import(TestUserDetailsManagerConfig.class)
public class InternalAuthenticationTest {
#Autowired WebApplicationContext context;
MockMvc mvc;
#BeforeEach
void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(SecurityMockMvcConfigurers.springSecurity())
.build();
}
#Test
void form_login_redirects_role_admin_to_page_admin_after_authentication() throws Exception {
MvcResult result = mvc
.perform(SecurityMockMvcRequestBuilders
.formLogin()
.loginProcessingUrl("/authenticate")
.user("identifier", "test-admin#example.com")
.password("password", "test-admin"))
.andExpect(MockMvcResultMatchers.redirectedUrl(AUTH_LOGIN_SUCCESS_ADMIN_REDIRECT_URL))
.andExpect(SecurityMockMvcResultMatchers.authenticated()
.withUsername("test-admin#example.com").withRoles("ADMIN")
.withAuthentication(auth -> assertThat(auth).isInstanceOf(UsernamePasswordAuthenticationToken.class)))
.andReturn();
}
}
My naive approach unfortunately does not work, and as the log shows, the authentication checks are done against the application provider, but not against the TestUserDetailsManager:
Invoking InternalUsernamePasswordAuthenticationFilter (7/12)
Authenticating request with InternalAuthenticationProvider (1/2)
Failed to find user credential for email 'test-admin#example.com'
Authenticating request with $Proxy157 (2/2)
Failed to find user 'test-admin#example.com'
Failed to process authentication request
-> Bad credentials
My question now:
How can I inject the TestUserDetailsManager into the CustomProviderManager so that the authentication (not authorization) tests work with special test users?
edit:
The question somewhat more generally:
How can I test the authentication of a Spring Boot application using a special UserDetailsManager for test cases only?
Many thanks in advance
I recently asked a question very similar to this one but instead of 401 the error I was getting was 403 (Forbbiden), but I changed the entire code so I decided to post a new one specific to this code and this problem.
I'm trying to create an user logic to my project (for the first time ever) but it has been impossible to implement any kind of security measure. I've been stuck in this for days so if anyone knows where I'm wrong I'd be grateful!
this is my code:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/users/create", "/users/create/**").permitAll()
.and()
.httpBasic();
}
}
#Data
#Component
public class CreateUserRoleDTO {
private Integer idUser;
private List<Integer> idsRoles;
public CreateUserRoleDTO() {
super();
}
public CreateUserRoleDTO(Integer idUser, List<Integer> idsRoles) {
super();
this.idUser = idUser;
this.idsRoles = idsRoles;
}
public Integer getIdUser() {
return idUser;
}
public void setIdUser(Integer idUser) {
this.idUser = idUser;
}
public List<Integer> getIdsRoles() {
return idsRoles;
}
public void setIdsRoles(List<Integer> idsRoles) {
this.idsRoles = idsRoles;
}
}
#Service
public class CreateRoleUserService {
#Autowired
private UserRepository repo;
#Autowired
private CreateUserRoleDTO createUserRoleDTO;
public Users execute(CreateUserRoleDTO createUserRoleDTO) {
Optional<Users> userExists=repo.findById(createUserRoleDTO.getIdUser());
List<Roles> roles=new ArrayList<>();
if (userExists.isEmpty()) {
throw new Error("User does not exist");
}
roles=createUserRoleDTO.getIdsRoles().stream().map(role -> {
return new Roles(role);
}).collect(Collectors.toList());
Users user=userExists.get();
user.setRole(roles);
repo.save(user);
return user;
}
#Entity
#Table(name="users_table")
public class Users implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
#Column(unique=true)
private String login;
#Column(unique=true)
private String email;
private String password;
#ManyToMany
private List<Roles> role;
}
(plus the getters and setters and constructors)
data.sql:
INSERT INTO `ROLES`(`ID`, `NAME`) VALUES(1, 'USER');
INSERT INTO `ROLES`(`ID`,`NAME`) VALUES(2, 'ADMIN');
-> the code runs fine, it even gives me the security password, the problem appears when I try to make any kind of requests.
The entire code if I've left anything out: https://github.com/vitoriaacarvalho/backend-challenge-very-useful-tools-to-remember-
An authentication configuration is missing in your SecurityConfig. For example, try adding the following to your configure method:
http.httpBasic();
Additionally, your security configuration is missing a default authorization rule, so authentication is not actually required. You can try adding .anyRequest().authenticated() to test this out.
Here's a configuration which uses the lambda syntax available in the DSL and is ready to be upgraded to Spring Security 6:
#Configuration
#EnableWebSecurity
#EnableMethodSecurity
public class SecurityConfig {
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.antMatchers("/users/create", "/users/create/**").permitAll()
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults());
// Disable CSRF for testing.
// TODO: Delete the following line and learn about CSRF!
http.csrf().disable();
return http.build();
}
#Bean // Automatically injected into Spring Security
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// Note: We don't configure a UserDetailsService since it is already
// annotated #Service and therefore already published as an #Bean.
}
Unfortunately, I also spotted a few other mistakes in your application that made it not work.
It looks like you have a mistake in the JPQL used to query the user for the UserDetailsService. The WHERE clause should be where u.login = :username (add a u.).
You also have the if-statement inverted as well. When throwing a UsernameNotFoundException (a better exception than Error for this case), it would look like:
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Users user = repo.findByUsernameFetchRoles(username);
if (user == null) {
throw new UsernameNotFoundException("User does not exist!");
}
return UserPrincipal.create(user);
}
Lastly, the constructor of your Users class was not assigning user data from the user parameter. It should be:
public UserPrincipal(Users user) {
this.login = user.getLogin();
this.password = user.getPassword();
...
}
With those changes, authentication works and you're on your way to learning Spring Security!
I'm currently using RemoteTokenServices class:
#Configuration
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Value("${auth-server.url}")
private String authEndpoint;
#Value("${security.oauth2.client.client-id}")
private String clientId;
#Value("${security.oauth2.client.client-secret}")
private String clientSecret;
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("ms/legacy");
}
#Bean
public ResourceServerTokenServices tokenService() {
RemoteTokenServices tokenServices = new RemoteTokenServices();
tokenServices.setClientId(clientId);
tokenServices.setClientSecret(clientSecret);
tokenServices.setCheckTokenEndpointUrl(authEndpoint + "/uaa/oauth/check_token");
return tokenServices;
}
}
I want to be able to mock this easily and properly for all my endpoints integration tests, knowing that:
the JWT is decoded in a OncePerRequestFilter to get some crucial info
I'm not interested in testing auth failures (well I am but that's not something that we want to do on each endpoint)
Is there a standard way to:
Produce a JWT token by hand ?
Mock all token service accesses easily ?
The expected result would be that I can write an endpoint test with only a few extra lines to setup the right JWT in the request, and the token service would agree on its validity dumbly.
Given that we don't want to test security at all, the best solution for this kind of case is to:
use standard Spring tests security management #WithMockUser along with MockMvc
adapt the ResourceServerConfigurerAdapter for tests:
create a base class that hosts all the config except for tokens
create an inheriting class for non-tests profiles (#ActiveProfiles("!test")) that hosts the token specific configuration
create an inheriting class for test profile that deactivates the remote token check (security.stateless(false);)
make the test classes use test profile
inject the proper token-extracted infos at the right time in tests
Here is how it was implemented in practice:
Base ResourceServerConfigurerAdapter so that the configuration has a major common part between tests and non-tests contexts:
public class BaseResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("ms/legacy");
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().permitAll().and().cors().disable().csrf().disable().httpBasic().disable()
.exceptionHandling()
.authenticationEntryPoint(
(request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
.accessDeniedHandler(
(request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED));
}
}
Its implementation outside for non-test:
#Configuration
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Profile("!test")
public class ResourceServerConfiguration extends BaseResourceServerConfiguration {
#Value("${auth-server.url}")
private String authEndpoint;
#Value("${security.oauth2.client.client-id}")
private String clientId;
#Value("${security.oauth2.client.client-secret}")
private String clientSecret;
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("ms/legacy");
}
#Bean
public ResourceServerTokenServices tokenService() {
RemoteTokenServices tokenServices = new RemoteTokenServices();
tokenServices.setClientId(clientId);
tokenServices.setClientSecret(clientSecret);
tokenServices.setCheckTokenEndpointUrl(authEndpoint + "/uaa/oauth/check_token");
return tokenServices;
}
}
And for tests:
#Configuration
#EnableResourceServer
#ActiveProfiles("test")
public class TestResourceServerConfigurerAdapter extends BaseResourceServerConfiguration {
#Override
public void configure(ResourceServerSecurityConfigurer security) throws Exception {
super.configure(security);
// Using OAuth with distant authorization service, stateless implies that the request tokens
// are verified each time against this service. In test, we don't want that because we need
// properly isolated tests. Setting this implies that the security is checked only locally
// and allows us to mock it with #WithMockUser, #AutoConfigureMockMvc and autowired MockMVC
security.stateless(false);
}
}
Inject token specific info with a request filter for tests:
#Component
#ActiveProfiles("test")
public class TestRequestFilter extends OncePerRequestFilter {
private Optional<InfoConf> nextInfoConf = Optional.empty();
// Request info is our request-scoped bean that holds JWT info
#Autowired
private RequestInfo info;
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
if (nextInfoConf.isPresent()) {
info.setInfoConf(nextInfoConf.get());
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
public void setNextInfoConf(InfoConf nextInfoConf) {
this.nextInfoConf = Optional.of(nextInfoConf);
}
public void clearNextInfoConf() {
nextInfoConf = Optional.empty();
}
}
And of course make the JWT parsing do nothing when there's no JWT.
We also wrote a small utility component to create the relevant info to inject.
A typical integration test will be like this:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
#ActiveProfiles("test")
public class TestClass {
#Autowired
protected MockMvc mockMvc;
#Before
public void before() {
// Create an user in DB
// Inject the related information in our filter
}
#After
public void after() {
// Cleanup both in DB and filter
}
#Test
#WithMockUser
public void testThing() throws Exception {
// Use MockMVC
}
}
Another solution is to indeed mock the ResourceServerTokenServices but in fact it's much more a pain to build proper tokens, and using Spring's standard security mock seems much more appropriate.
I'm seeing the following message on a Spring Boot app startup:
> *************************** APPLICATION FAILED TO START
> ***************************
>
> Description:
>
> Field oauthProps in com.example.authservice.AuthorizationServerConfig
> required a single bean, but 2 were found:
> - OAuthProperties: defined in file [/Users/simeonleyzerzon/abc/spring-security/spring-security-5-oauth-client/auth-service/target/classes/com/example/authservice/config/OAuthProperties.class]
> - kai-com.example.authservice.config.OAuthProperties: defined in null
>
>
> Action:
>
> Consider marking one of the beans as #Primary, updating the consumer
> to accept multiple beans, or using #Qualifier to identify the bean
> that should be consumed
I'm wondering what's causing the duplication of that bean and how one can remove it without the necessity of using the #Primary annotation? Not sure where the kai-com package(?) from the above is coming from.
Here's the bean in question:
package com.example.authservice.config;
//#Primary
#Component
#ConfigurationProperties(prefix="kai")
#Setter #Getter
public class OAuthProperties {
private String[] redirectUris;
private String clientId;
private String clientSecret;
private final Token token = new Token();
#Setter #Getter
public static class Token{
private String value;
private String type="";
}
}
and the app/config, etc.:
package com.example.authservice;
import ...
#SpringBootApplication
public class AuthServiceApplication {
public static void main(String[] args) {
SpringApplication.run(AuthServiceApplication.class, args);
}
}
#Controller
class MainController {
#GetMapping("/")
String index() {
return "index";
}
}
#RestController
class ProfileRestController {
#GetMapping("/resources/userinfo")
Map<String, String> profile(Principal principal) {
return Collections.singletonMap("name", principal.getName());
}
}
#Configuration
#EnableResourceServer
class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/resources/**")
.authorizeRequests()
.mvcMatchers("/resources/userinfo").access("#oauth2.hasScope('profile')");
}
}
#Configuration
#EnableAuthorizationServer
#EnableConfigurationProperties(OAuthProperties.class)
class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired private OAuthProperties oauthProps;
private final AuthenticationManager authenticationManager;
AuthorizationServerConfig(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient(oauthProps.getClientId())
.secret(oauthProps.getClientSecret())
.authorizedGrantTypes("authorization_code")
.scopes("profile")
.redirectUris(oauthProps.getRedirectUris());
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(this.authenticationManager);
if (oauthProps.getToken().getType().equals("jwt")) {
endpoints.tokenStore(this.tokenStore()).accessTokenConverter(jwtAccessTokenConverter());
}else {
endpoints.tokenEnhancer(eapiTokenEnhancer());
}
}
TokenEnhancer eapiTokenEnhancer() {
return new TokenEnhancer() {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
DefaultOAuth2AccessToken result = new DefaultOAuth2AccessToken(accessToken);
result.setValue(oauthProps.getToken().getValue());
return result;
}
};
}
#Bean
JwtAccessTokenConverter jwtAccessTokenConverter() {
KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource(".keystore-oauth2-demo"), //keystore
"admin1234".toCharArray()); //storepass
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setKeyPair(factory.getKeyPair("oauth2-demo-key")); //alias
return jwtAccessTokenConverter;
}
#Bean
TokenStore tokenStore() {
return new JwtTokenStore(this.jwtAccessTokenConverter());
}
}
#Service
class SimpleUserDetailsService implements UserDetailsService {
private final Map<String, UserDetails> users = new ConcurrentHashMap<>();
SimpleUserDetailsService() {
Arrays.asList("josh", "rob", "joe")
.forEach(username -> this.users.putIfAbsent(
username, new User(username, "pw", true, true, true, true, AuthorityUtils.createAuthorityList("USER","ACTUATOR"))));
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return this.users.get(username);
}
}
#Configuration
#EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
Eclipse too seems to be only aware of a single instance of the bean:
When using #EnableConfigurationProperties with #ConfigurationProperties you will get a bean named <prefix>-<fqn>, the kai-com.example.authservice.config.OAuthProperties. (See also the reference guide).
When the #ConfigurationProperties bean is registered that way, the bean has a conventional name: <prefix>-<fqn>, where <prefix> is the environment key prefix specified in the #ConfigurationProperties annotation and <fqn> is the fully qualified name of the bean. If the annotation does not provide any prefix, only the fully qualified name of the bean is used.
The bean name in the example above is acme-com.example.AcmeProperties. (From the Reference Guide).
The #Component will lead to another registration of the bean with the regular name of the classname with a lowercase character. The other instance of your properties.
the #EnableConfigurationProperties annotation is also automatically applied to your project so that any existing bean annotated with #ConfigurationProperties is configured from the Environment. You could shortcut MyConfiguration by making sure AcmeProperties is already a bean, as shown in the following example: (From the Reference Guide).
The key here is that #EnableConfigurationProperties is already globally applied and processes any bean annotated with #ConfigurationProperties.
So basically you where mixing the 2 ways of using #ConfigurationProperties and Spring Boot 2 now prevents that misuse. This way you write better code (and reduce the memory footprint and performance slightly).
So either remove the #Component or remove the #EnableConfigurationProperties, either way will work.
The following change (removing of #EnableConfigurationProperties) seems to help relieving the need for the #Primary annotation:
#Configuration
#EnableAuthorizationServer
//#EnableConfigurationProperties(OAuthProperties.class)
class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired private OAuthProperties oauthProps;
Perhaps someone can describe the internal Spring mechanics of secondary bean creation (and its namespace/package assignment) by that annotation which seemingly causes the collision with the #Autowired one, or point me to the appropriate documentation of this behavior.
I need to allow users to get an OAuth token via grant_type=password and using GET and not POST. The default implementation for TokenEndpoint is as follows:
public class TokenEndpoint extends AbstractEndpoint {
private OAuth2RequestValidator oAuth2RequestValidator = new DefaultOAuth2RequestValidator();
private Set<HttpMethod> allowedRequestMethods = new HashSet<HttpMethod>(Arrays.asList(HttpMethod.POST));
#RequestMapping(value = "/oauth/token", method=RequestMethod.GET)
public ResponseEntity<OAuth2AccessToken> getAccessToken(Principal principal, #RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!allowedRequestMethods.contains(HttpMethod.GET)) {
throw new HttpRequestMethodNotSupportedException("GET");
}
return postAccessToken(principal, parameters);
}
As you can see, the default allowed is only POST. I am using XML configuration (not annotations). How can I add to the Set the HttpMethod.GET?
The following config worked:
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.requestFactory(defaultOAuth2RequestFactory)
.authenticationManager(myUserAuthenticationManager)
.tokenStore(myTokenStore)
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);// to allow get for password grant
;
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.realm(REALM)
.allowFormAuthenticationForClients()// to let users do password grant with username/ password on get
;
}
With just XML config it's not possible to configure the allowed token end point methods.
That leaves you with two options:
move everything to Java config (as checklist's answer)
create an extra configuration class that will run a #PostConstruct method after the XML has run, to finish the job.
Java config is probably what you should be using for a new app, but if you've got an older app that's using XML config, then something like this will work:
#Configuration
public class AllowedMethodConfig {
#Autowired
private TokenEndpoint tokenEndpoint;
#PostConstruct
public void reconfigure() {
Set<HttpMethod> allowedMethods =
new HashSet<>(Arrays.asList(HttpMethod.GET, HttpMethod.POST));
tokenEndpoint.setAllowedRequestMethods(allowedMethods);
}
}