I have followed the following links to try and test OAuth2 #PreAuthorise(hasAnyRole('ADMIN', 'TEST') for example but I can't any of the tests to pass or even authenticate.
When I try to access the end point with admin (or any role) it will never authenticate properly. Am I missing something obvious, it seems I have everything just as it is in the examples. I have also tried another alternative to the WithSecurityContext Factory with OAuth Specific Authentication and still no luck. Any help would be appreciated.
https://stackoverflow.com/a/31679649/2594130
and
http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test
My Controller I'm testing
#RestController
#RequestMapping("/bookmark/")
public class GroupBookmarkController {
#Autowired
BookmarkService bookmarkService;
/**
* Get list of all bookmarks
*/
#RequestMapping(value = "{groupId}", method = RequestMethod.GET)
#PreAuthorize("hasAnyRole(['ADMIN', 'USER'])")
public ResponseEntity<List<Bookmark>> listAllGroupBookmarks(#PathVariable("groupId") String groupId) throws BookmarkNotFoundException {
List<Bookmark> bookmarks = bookmarkService.findAllBookmarksByGroupId(groupId);
return new ResponseEntity<>(bookmarks, HttpStatus.OK);
}
...
}
My Test class
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = BookmarkServiceApplication.class)
#WebAppConfiguration
public class BookmarkServiceApplicationTests {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Before
public void loadData() {
this.mockMvc = MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.apply(springSecurity())
.alwaysDo(print())
.build();
}
#Test
#WithMockCustomUser(username = "test")
public void getBookmarkAuthorised() throws Exception {
mockMvc.perform(get("/bookmark/nvjdbngkjlsdfngkjlfdsnlkgsd"))
.andExpect(status().is(HttpStatus.SC_OK));
// always 401 here
}
}
My BookmarkServiceApplication
#SpringBootApplication
#EnableResourceServer
public class BookmarkServiceApplication {
public static void main(String[] args) {
SpringApplication.run(BookmarkServiceApplication.class, args);
}
}
My WithSecurityContextFactory
public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser> {
#Override
public SecurityContext createSecurityContext(WithMockCustomUser customUser) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
UserDetails principal = new User(customUser.username(), "password", true, true, true, true, grantedAuthorities);
Authentication authentication = new UsernamePasswordAuthenticationToken(
principal, principal.getPassword(), principal.getAuthorities());
context.setAuthentication(authentication);
return context;
}
}
My WithSecurityContext Annotation
#Retention(RetentionPolicy.RUNTIME)
#WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public #interface WithMockCustomUser {
String username() default "user";
String name() default "Test User";
}
As per #RobWinch 's reply
Hi #RobWinch I've tried you suggestion with the stateless flag, this helped with part of the answer. However in your reply to this question [Spring OAuth and Boot Integration Test] (https://stackoverflow.com/a/31679649/2594130) you mention
You no longer need to worry about running in stateless mode or not
Why is it that I need to still add the stateless false, is this a bug or are we using it slightly differently?
The other thing I needed to do to get this to work was adding OAuth2Request and OAuth2Authentication to the WithSecurityContextFactory as you can see in the following
public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockOAuthUser> {
#Override
public SecurityContext createSecurityContext(WithMockOAuthUser withClient) {
// Get the username
String username = withClient.username();
if (username == null) {
throw new IllegalArgumentException("Username cannot be null");
}
// Get the user roles
List<GrantedAuthority> authorities = new ArrayList<>();
for (String role : withClient.roles()) {
if (role.startsWith("ROLE_")) {
throw new IllegalArgumentException("roles cannot start with ROLE_ Got " + role);
}
authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
}
// Get the client id
String clientId = withClient.clientId();
// get the oauth scopes
String[] scopes = withClient.scope();
Set<String> scopeCollection = Sets.newSet(scopes);
// Create the UsernamePasswordAuthenticationToken
User principal = new User(username, withClient.password(), true, true, true, true, authorities);
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(),
principal.getAuthorities());
// Create the authorization request and OAuth2Authentication object
OAuth2Request authRequest = new OAuth2Request(null, clientId, null, true, scopeCollection, null, null, null,
null);
OAuth2Authentication oAuth = new OAuth2Authentication(authRequest, authentication);
// Add the OAuth2Authentication object to the security context
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(oAuth);
return context;
}
}
The problem is that OAuth2AuthenticationProcessingFilter will clear the SecurityContext if it is marked as stateless. To workaround this configure it to allow the state to be populated externally (i.e. stateless = false).
to add some more infos how to set stateless to false:
in your ResourceServerConfigurerAdapter do the following:
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.stateless(false);
}
which worked for me.
Related
The documentation at spring security is missing important detail. Our idp does not provide an introspection link, and our resource server is not a client in its own right. It receives JWT access tokens from the actual client, and "needs to know" details about the user associated with the access token.
In our case standard jwt processing gives us a useful start, but we need to fill out the authentication with the claims from userinfo.
How do we 1. get a baseline valid oauth2 authentication, 2. fill it out with the results of the userinfo call.
public class UserInfoOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
private final OpaqueTokenIntrospector delegate =
new NimbusOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret");
private final WebClient rest = WebClient.create();
#Override
public OAuth2AuthenticatedPrincipal introspect(String token) {
OAuth2AuthenticatedPrincipal authorized = this.delegate.introspect(token);
return makeUserInfoRequest(authorized);
}
}
Current implementation using a converter:
#Configuration
public class JWTSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired JwtConverterWithUserInfo jwtConverter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable()
.authorizeRequests(authz -> authz
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll())
.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtConverter);
}
}
#Configuration
public class WebClientConfig {
/**
* Provides a Web-Client Bean containing the bearer token of the authenticated user.
*/
#Bean
WebClient webClient(){
HttpClient httpClient = HttpClient.create()
.responseTimeout(Duration.ofSeconds(5))
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.filter(new ServletBearerExchangeFilterFunction())
.build();
}
}
#Component
#Log4j2
public class JwtConverterWithUserInfo implements Converter<Jwt, AbstractAuthenticationToken> {
#Autowired WebClient webClient;
#Value("${userinfo-endpoint}")
String userinfoEndpoint;
#SuppressWarnings("unchecked")
#Override
public AbstractAuthenticationToken convert(Jwt jwt) {
String token = jwt.getTokenValue();
log.debug("Calling userinfo endpoint for token: {}", token);
String identityType = jwt.getClaimAsString("identity_type");
Map<String,Object> userInfo = new HashMap<>();
if ("user".equals(identityType)) {
// invoke the userinfo endpoint
userInfo =
webClient.get()
.uri(userinfoEndpoint)
.headers(h -> h.setBearerAuth(token))
.retrieve()
.onStatus(s -> s.value() >= HttpStatus.SC_BAD_REQUEST, response -> response.bodyToMono(String.class).flatMap(body -> {
return Mono.error(new HttpException(String.format("%s, %s", response.statusCode(), body)));
}))
.bodyToMono(Map.class)
.block();
log.debug("User info Map is: {}",userInfo);
// construct an Authentication including the userinfo
OidcIdToken oidcIdToken = new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims());
OidcUserInfo oidcUserInfo = new OidcUserInfo(userInfo);
List<OidcUserAuthority> authorities = new ArrayList<>();
if (oidcIdToken.hasClaim("scope")) {
String scope = String.format("SCOPE_%s", oidcIdToken.getClaimAsString("scope"));
authorities.add(new OidcUserAuthority(scope, oidcIdToken, oidcUserInfo));
}
OidcUser oidcUser = new DefaultOidcUser(authorities, oidcIdToken, oidcUserInfo, IdTokenClaimNames.SUB);
//TODO replace this OAuth2 Client authentication with a more appropriate Resource Server equivalent
return new OAuth2AuthenticationTokenWithCredentials(oidcUser, authorities, oidcUser.getName());
} else {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
if (jwt.hasClaim("scope")) {
authorities.add(new SimpleGrantedAuthority(String.format("SCOPE_%s", jwt.getClaimAsString("scope"))));
}
return new JwtAuthenticationToken(jwt, authorities);
}
}
}
public class OAuth2AuthenticationTokenWithCredentials extends OAuth2AuthenticationToken {
public OAuth2AuthenticationTokenWithCredentials(OAuth2User principal,
Collection<? extends GrantedAuthority> authorities,
String authorizedClientRegistrationId) {
super(principal, authorities, authorizedClientRegistrationId);
}
#Override
public Object getCredentials() {
return ((OidcUser) this.getPrincipal()).getIdToken();
}
}
Instead of a custom OpaqueTokenIntrospector, try a custom JwtAuthenticationConverter:
#Component
public class UserInfoJwtAuthenticationConverter implements Converter<Jwt, BearerTokenAuthentication> {
private final ClientRegistrationRepository clients;
private final JwtGrantedAuthoritiesConverter authoritiesConverter =
new JwtGrantedAuthoritiesConverter();
#Override
public BearerTokenAuthentication convert(Jwt jwt) {
// Spring Security has already verified the JWT at this point
OAuth2AuthenticatedPrincipal principal = invokeUserInfo(jwt);
Instant issuedAt = jwt.getIssuedAt();
Instant expiresAt = jwt.getExpiresAt();
OAuth2AccessToken token = new OAuth2AccessToken(
BEARER, jwt.getTokenValue(), issuedAt, expiresAt);
Collection<GrantedAuthority> authorities = this.authoritiesConverter.convert(jwt);
return new BearerTokenAuthentication(principal, token, authorities);
}
private OAuth2AuthenticatedPrincipal invokeUserInfo(Jwt jwt) {
ClientRegistration registration =
this.clients.findByRegistrationId("registration-id");
OAuth2UserRequest oauth2UserRequest = new OAuth2UserRequest(
registration, jwt.getTokenValue());
return this.oauth2UserService.loadUser(oauth2UserRequest);
}
}
And then wire into the DSL like so:
#Bean
SecurityFilterChain web(
HttpSecurity http, UserInfoJwtAuthenticationConverter authenticationConverter) {
http
.oauth2ResourceServer((oauth2) -> oauth2
.jwt((jwt) -> jwt.jwtAuthenticationConverter())
);
return http.build();
}
our resource server is not a client in its own right
oauth2-client is where Spring Security's support for invoking /userinfo lives and ClientRegistration is where the application's credentials are stored for addressing /userinfo. If you don't have those, then you are on your own to invoke the /userinfo endpoint yourself. Nimbus provides good support, or you may be able to simply use RestTemplate.
I have a situation where the authorisation server is not returning expires_in field to the token response, but the token expires after certain time. Can I set this manually somewhere in my code ?
Below is my code for ROPC.
#Bean(name = “myROPCRestTemplate")
public OAuth2RestTemplate myROPCRestTemplate() {
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(myPasswordResourceDetails());
restTemplate.setAccessTokenProvider(getAccessTokenProvider());
return restTemplate;
}
private AccessTokenProvider getAccessTokenProvider() {
ResourceOwnerPasswordAccessTokenProvider resourceOwnerPasswordAccessTokenProvider = new ResourceOwnerPasswordAccessTokenProvider();
return new AccessTokenProviderChain(Collections.singletonList(resourceOwnerPasswordAccessTokenProvider));
}
private OAuth2ProtectedResourceDetails myPasswordResourceDetails() {
ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
resource.setAccessTokenUri(tokenUrl);
resource.setClientId(clientId);
resource.setClientSecret(clientSecret);
resource.setUsername(username);
resource.setPassword(password);
resource.setClientAuthenticationScheme(AuthenticationScheme.form);
resource.setGrantType("password");
return resource;
}
I know this is an old question but maybe someone need to override AccessToken implementation which is DefaultOAuth2AccessToken under spring security oauth2 autoconfigure project, here is the one workaround that we used
Our approach was not extend default access token or override new accesstoken from scratch with using OAuth2AccessToken, instead create ClientContext which is extend DefaultOAuth2ClientContext and make necessary changes on same AccessToken during set operation.
Here is the code sample, first extends client context, create a new component and make neccessary changes in setAccessToken (in this case setting exiparation) :
#Component
public class MyOAuth2ClientContext extends DefaultOAuth2ClientContext {
#Override
public void setAccessToken(OAuth2AccessToken accessToken) {
DefaultOAuth2AccessToken dxpAccessToken = new DefaultOAuth2AccessToken(accessToken);
dxpAccessToken.setExpiration(new Date());
super.setAccessToken(dxpAccessToken);
}
}
And finaly use this context when constructing your OAuth2RestTemplate use your own context :
#Configuration
public class MyWebConfiguration {
#Resource MyOAuth2ClientContext myOAuth2ClientContext;
#Bean
#ConfigurationProperties("spring.security.oauth2.client.authserver")
protected ClientCredentialsResourceDetails authServerDetails() {
return new ClientCredentialsResourceDetails();
}
#Bean(name = "myRestTemplate")
protected RestTemplate myRestTemplate() {
return new OAuth2RestTemplate(authServerDetails(), myOAuth2ClientContext);
}
}
Hope this will be helpful.
You could register a DefaultTokenServices bean and configure it:
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setAccessTokenValiditySeconds(3600); // valid for one hour
return defaultTokenServices;
}
I'm developing a rest service which is going to be available in browser via
browser single page app and a mobile app. At the moment my service is working
without spring at all. The oauth2 client is implemented inside filters so to say "by hand".
I'm trying to migrate it to spring boot.
Much manuals read and much info googled and I'm trying to understand if the
following is actually possible for a customer:
Authorize with facebook oauth2 service (and get an access_token) with all the help
from spring-security-oauth2.
Create a JWT and pass it to the client so that all further requests are
backed with the JWT.
Since in my opinion spring boot is all about the configuration and declarations
I want to understand if this is possible with spring-security-oauth2 and
spring-security-jwt?
I'm not askng for a solution but just a yes/no from knowledge bearers since I'm deep in
the spring manuals and the answer becomes further...
short answer: Yes you can do it!
You have to add security dependencies to your build.gradle or pom.xml file:
compile "org.springframework.boot:spring-boot-starter-security"
compile "org.springframework.security:spring-security-config"
compile "org.springframework.security:spring-security-data"
compile "org.springframework.security:spring-security-web"
compile "org.springframework.social:spring-social-security"
compile "org.springframework.social:spring-social-google"
compile "org.springframework.social:spring-social-facebook"
compile "org.springframework.social:spring-social-twitter"
then you have to add social config to your project alongside with your security config:
#Configuration
#EnableSocial
public class SocialConfiguration implements SocialConfigurer {
private final Logger log = LoggerFactory.getLogger(SocialConfiguration.class);
private final SocialUserConnectionRepository socialUserConnectionRepository;
private final Environment environment;
public SocialConfiguration(SocialUserConnectionRepository socialUserConnectionRepository,
Environment environment) {
this.socialUserConnectionRepository = socialUserConnectionRepository;
this.environment = environment;
}
#Bean
public ConnectController connectController(ConnectionFactoryLocator connectionFactoryLocator,
ConnectionRepository connectionRepository) {
ConnectController controller = new ConnectController(connectionFactoryLocator, connectionRepository);
controller.setApplicationUrl(environment.getProperty("spring.application.url"));
return controller;
}
#Override
public void addConnectionFactories(ConnectionFactoryConfigurer connectionFactoryConfigurer, Environment environment) {
// Google configuration
String googleClientId = environment.getProperty("spring.social.google.client-id");
String googleClientSecret = environment.getProperty("spring.social.google.client-secret");
if (googleClientId != null && googleClientSecret != null) {
log.debug("Configuring GoogleConnectionFactory");
connectionFactoryConfigurer.addConnectionFactory(
new GoogleConnectionFactory(
googleClientId,
googleClientSecret
)
);
} else {
log.error("Cannot configure GoogleConnectionFactory id or secret null");
}
// Facebook configuration
String facebookClientId = environment.getProperty("spring.social.facebook.client-id");
String facebookClientSecret = environment.getProperty("spring.social.facebook.client-secret");
if (facebookClientId != null && facebookClientSecret != null) {
log.debug("Configuring FacebookConnectionFactory");
connectionFactoryConfigurer.addConnectionFactory(
new FacebookConnectionFactory(
facebookClientId,
facebookClientSecret
)
);
} else {
log.error("Cannot configure FacebookConnectionFactory id or secret null");
}
// Twitter configuration
String twitterClientId = environment.getProperty("spring.social.twitter.client-id");
String twitterClientSecret = environment.getProperty("spring.social.twitter.client-secret");
if (twitterClientId != null && twitterClientSecret != null) {
log.debug("Configuring TwitterConnectionFactory");
connectionFactoryConfigurer.addConnectionFactory(
new TwitterConnectionFactory(
twitterClientId,
twitterClientSecret
)
);
} else {
log.error("Cannot configure TwitterConnectionFactory id or secret null");
}
// jhipster-needle-add-social-connection-factory
}
#Override
public UserIdSource getUserIdSource() {
return new AuthenticationNameUserIdSource();
}
#Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
return new CustomSocialUsersConnectionRepository(socialUserConnectionRepository, connectionFactoryLocator);
}
#Bean
public SignInAdapter signInAdapter(UserDetailsService userDetailsService, JHipsterProperties jHipsterProperties,
TokenProvider tokenProvider) {
return new CustomSignInAdapter(userDetailsService, jHipsterProperties,
tokenProvider);
}
#Bean
public ProviderSignInController providerSignInController(ConnectionFactoryLocator connectionFactoryLocator, UsersConnectionRepository usersConnectionRepository, SignInAdapter signInAdapter) {
ProviderSignInController providerSignInController = new ProviderSignInController(connectionFactoryLocator, usersConnectionRepository, signInAdapter);
providerSignInController.setSignUpUrl("/social/signup");
providerSignInController.setApplicationUrl(environment.getProperty("spring.application.url"));
return providerSignInController;
}
#Bean
public ProviderSignInUtils getProviderSignInUtils(ConnectionFactoryLocator connectionFactoryLocator, UsersConnectionRepository usersConnectionRepository) {
return new ProviderSignInUtils(connectionFactoryLocator, usersConnectionRepository);
}
}
then you have to write adapter for your social login:
public class CustomSignInAdapter implements SignInAdapter {
#SuppressWarnings("unused")
private final Logger log = LoggerFactory.getLogger(CustomSignInAdapter.class);
private final UserDetailsService userDetailsService;
private final JHipsterProperties jHipsterProperties;
private final TokenProvider tokenProvider;
public CustomSignInAdapter(UserDetailsService userDetailsService, JHipsterProperties jHipsterProperties,
TokenProvider tokenProvider) {
this.userDetailsService = userDetailsService;
this.jHipsterProperties = jHipsterProperties;
this.tokenProvider = tokenProvider;
}
#Override
public String signIn(String userId, Connection<?> connection, NativeWebRequest request){
try {
UserDetails user = userDetailsService.loadUserByUsername(userId);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
user,
null,
user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
String jwt = tokenProvider.createToken(authenticationToken, false);
ServletWebRequest servletWebRequest = (ServletWebRequest) request;
servletWebRequest.getResponse().addCookie(getSocialAuthenticationCookie(jwt));
} catch (AuthenticationException ae) {
log.error("Social authentication error");
log.trace("Authentication exception trace: {}", ae);
}
return jHipsterProperties.getSocial().getRedirectAfterSignIn();
}
private Cookie getSocialAuthenticationCookie(String token) {
Cookie socialAuthCookie = new Cookie("social-authentication", token);
socialAuthCookie.setPath("/");
socialAuthCookie.setMaxAge(10);
return socialAuthCookie;
}
}
you can find sample project in my github:
https://github.com/ksadjad/oauth-test
i am trying to automatically authorization without login in spring security. The user would be authorized by clicking a link in a website.
I have a class UserLoginService that called from spring-security xml file like this;
<authentication-manager>
<authentication-provider user-service-ref="userLoginService" >
<password-encoder hash="md5"/>
</authentication-provider>
</authentication-manager>
<beans:bean id="userLoginService"
class="tr.com.enlil.formdesigner.server.guvenlik.UserLoginService">
</beans:bean>
UserLoginService class;
public class UserLoginService implements UserDetailsService {
private static Logger logger = Logger.getLogger(InitServlet.class);
#Autowired
private IKullaniciBusinessManager iKullaniciBusinessManager;
/**
* {#inheritDoc}
*/
#Override
public UserDetails loadUserByUsername(String username) {
try {
Kullanici kullanici = new Kullanici();
kullanici.setKullaniciAdi(username);
Kullanici kullaniciBusinessManager = iKullaniciBusinessManager.getirKullaniciAdinaGore(kullanici);
User user = new User();
if (kullaniciBusinessManager != null && kullaniciBusinessManager.getAktifmi()) {
user.setUsername(kullaniciBusinessManager.getKullaniciAdi());
user.setPassword(kullaniciBusinessManager.getSifre());
user.setKullanici(kullaniciBusinessManager);
List<String> yetkiListesi = new ArrayList<String>();
List<GrantedAuthority> grandAuthorities = new ArrayList<GrantedAuthority>();
//TODO yetkilerle alakalı birşey yapmak gerekebilir.
for (String yetki : yetkiListesi) {
GrantedAuthorityImpl g = new GrantedAuthorityImpl(yetki);
grandAuthorities.add(g);
}
user.setAuthorities(grandAuthorities);
}
return user;
} catch (Exception e) {
logger.error("Kullanici alinirken hata olustu!!", e);
}
return null;
}
public static void autoLogin(User user, HttpServletRequest request, AuthenticationManager authenticationManager) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user.getUsername(),
user.getPassword(), user.getAuthorities());
// generate session if one doesn't exist
request.getSession();
token.setDetails(new WebAuthenticationDetails(request));
Authentication authenticatedUser = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authenticatedUser);
// setting role to the session
request.getSession().setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
SecurityContextHolder.getContext());
}
}
I found autoLogin method from Make Programmatic login without username/password?. But i dont know, from where can i call this method and will this code help me.
Thanks in advance.
You will have to create your own implementation of AbstractPreAuthenticatedProcessingFilter. The method getPreAuthenticatedPrincipal(HttpServletRequest request) will have the request where you can get your credentials from. You will need to return a subject if it is a valid user or null if it is not. Your implementation of UserDetailsService will transform the subject to a UserDetails object.
i give users special URL with access key in it. users accessing the public page via this special url should be able to see some additional data as compared to simple anonymous user.
i want to give some additional role to anonymous user based on parameters provided in request so i can do something like this in my template:
<#sec.authorize ifAnyGranted="ROLE_ADMIN, ROLE_USER, ROLE_INVITED_VISITOR">
...some additional stuff for invited user to see
</#sec.authorize>
currently i'm implementing Spring's OncePerRequestfilter:
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
if (null != request.getParameter("accessKey")) {
if(isValid(request.getParameter("accessKey"))) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
//how do i add additional roles to authenticated (potentially anonymous) user?
}
}
}
Why not just create a wrapper class that delegates to the original, but adds on a couple of extra GrantedAuthorities:
public class AuthenticationWrapper implements Authentication
{
private Authentication original;
private GrantedAuthority[] extraRoles;
public AuthenticationWrapper( Authentication original, GrantedAuthority[] extraRoles )
{
this.original = original;
this.extraRoles = extraRoles;
}
public GrantedAuthority[] getAuthorities()
{
GrantedAuthority[] originalRoles = original.getAuthorities();
GrantedAuthority[] roles = new GrantedAuthority[originalRoles.length + extraRoles.length];
System.arraycopy( originalRoles, 0, roles, 0, originalRoles.length );
System.arraycopy( extraRoles, 0, roles, originalRoles.length, extraRoles.length );
return roles;
}
public String getName() { return original.getName(); }
public Object getCredentials() { return original.getCredentials(); }
public Object getDetails() { return original.getDetails(); }
public Object getPrincipal() { return original.getPrincipal(); }
public boolean isAuthenticated() { return original.isAuthenticated(); }
public void setAuthenticated( boolean isAuthenticated ) throws IllegalArgumentException
{
original.setAuthenticated( isAuthenticated );
}
}
and then do this in your filter:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
GrantedAuthority extraRoles = new GrantedAuthority[2];
extraRoles[0] = new GrantedAuthorityImpl( "Role X" );
extraRoles[1] = new GrantedAuthorityImpl( "Role Y" );
AuthenticationWrapper wrapper = new AuthenticationWrapper( auth, extraRoles );
SecurityContextHolder.getContext().setAuthentication( wrapper );
The Authentication is now replaced by your version with the extra roles. NB You may have to handle the case where the Authentication has not yet been authenticated and so its getAuthorities() returns null. (The wrapper implementation currently assumes that it will always get a non-null array from its wrapped Authentication)