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);
}
}
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 have this to validate the method and resouce and role but I can not believe that this part need to set in code, exist a way to consult a table?
For example
table: user_access <br>
campos: url , path <br>
method="HttpMethod.GET" <br>
path="/api/clientes/page/**"
And I have this code:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
//implementar reglas de seguridad para los end points
//por el lado de oauth
#Override
public void configure(HttpSecurity http) throws Exception {
//reglas especificate al mas generico
**//I Dont like this part and i try to creat it dinamicly**
http.authorizeRequests().antMatchers(HttpMethod.GET,"/api/clientes","/api/clientes/page/*","/api/clientes/img/*","/images/**").permitAll()
.antMatchers(HttpMethod.GET,"/api/clientes/form/").hasAnyRole("ADMIN","SUPER_ADMIN")
.antMatchers(HttpMethod.POST,"/api/clientes/uploads").hasAnyRole("ADMIN","SUPER_ADMIN")
.antMatchers(HttpMethod.POST,"/api/clientes").hasAnyRole("ADMIN","SUPER_ADMIN")
.antMatchers(HttpMethod.DELETE,"/api/clientes/{id}").hasRole("SUPER_ADMIN")
.antMatchers(HttpMethod.GET,"/api/clientes/{id}").hasRole("SUPER_ADMIN")
.anyRequest().authenticated()
.and().cors().configurationSource(cousConfigurationSource()); //error en el cors con esto se arregla para la pagina de angular
}
//importante no tomar cors.reactive
#Bean
public CorsConfigurationSource cousConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(Arrays.asList("http://localhost:4200"));
config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS")); //se podria poner * para todo
config.setAllowCredentials(true);
config.setAllowedHeaders(Arrays.asList("Content-Type","Autorization"));
UrlBasedCorsConfigurationSource source= new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
//filtro //seleccionar spring Framework
#Bean
public FilterRegistrationBean<CorsFilter> corsFilter(){
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<CorsFilter>(new CorsFilter(cousConfigurationSource()));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
Any Idea to make it more dynamic?
Hope below will help,
You can do it but it will work when the application starts, You can create a table to hold all of required configs, and when the application start running, you can write a code to read all of configured values and use them to configure the HttpSecurity.
Example:
#Entity
public class UserAccessEntity{
#Id
private String url;
private String methodName;
private List<String> userRoles;
private boolean isPattern;
// setter and getter
}
public interface UserAccessRepository extends CrudRepository<UserAccessEntity,String> {}
Then just do the configuration after you fetch the records from the database as below:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private final UserAccessRepository repository;
public ResourceServerConfig(UserAccessRepository repository){
this.repository = repository;
}
// Configure this as you need.
#Override
public void configure(HttpSecurity http) throws Exception {
repository.findAll().foreach(userAccess -> {
http.authorizeRequests()
.antMatchers(userAccess.getMethodName(),userAccess.getUrl())
.hasAnyRole(userAccess.getRoles())
});
}
}
Only the limitation you have is that you can pre-configure all of the access before application get started, and you have to restart the server if you need to reflect any change of the user access records.
I think #PreAuthorize can make your validation more dynamically: You can validate for your controller, and using spEL(Spring expression language) to combine more cases.
To enable #PreAuthorize, you need to config in the WebSecurityConfig
#Configuration
#EnableWebSecurity
//enable for #PreAuthorize
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSercurityConfig extends WebSecurityConfigurerAdapter {
//configs
}
In my example, the 'SUPER_ADMIN' or user who owned the resources can access the data from the request(get the user details information). Hope this can be helpful for you.
#RestController
public class UserDetailsController {
#Autowired
private UsersService usersService;
#CrossOrigin(origins = "http://localhost:4200")
#PreAuthorize("hasAuthority('SUPER_ADMIN')" + "or hasAuthority('USER') and authentication.getName().equals(#username)")
#GetMapping(path = "/user/{username}")
public ResponseEntity<?> getUserDetailByUsername(#PathVariable("username") String username, Authentication authentication) {
return ResponseEntity.ok(usersService.getUserByUsername(username));
}
}
spEL references document: Spring Expression Language
, Enable #PreAuthorize reference: Introduction to Spring Method Security
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
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'd like create Spring request Interceptor which will be able to get some data from session and change some #Autowired components before request.
I can create Interceptor and register it, but it can't get access to session beans:
#Component
#Scope(value="session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class TokenInterceptor extends HandlerInterceptorAdapter {
#Autowired
private MyServicePerSession myServicePerSession;
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(myServicePerSession.getName()); // NullPointerException!!!
return true;
}
}
Above in the method preHandle(...) per each request I get NullPointerException.
Here is my config:
#Configuration
#EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
//...
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TokenInterceptor());
}
}
How I said everything work fine except injecting MyServicePerSession.
I will really appreciate if you can give me advice about it, or some other ways to solve that problem.
You are trying to set a new object but you have to set a spring bean.
new TokenInterceptor() // is not spring bean
#Autowired private TokenInterceptor tokenInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor);
// You have to set bean here
}
If this doesn't work, you can check this http://docs.spring.io/spring-javaconfig/docs/1.0.0.M4/reference/html/ch04s02.html