Spring Security OAuth2 - How to modify token response JSON? - spring-security-oauth2

I am getting the Response JSON (for JWT token request) as below:
{
"access_token": "<JWT Access Token>",
"token_type": "bearer",
"refresh_token": "<JWT Refresh Token>",
"expires_in": 3599,
"scope": "read write trust",
"DateOfBirth": "01-01-1990",
"Address_Line_1": "ABCD Andrews Dr, Apt 111",
"PAN_Number": "12345ABCD",
"Address_Line_2": "Dublin, CA 94588",
"jti": "e6a19730-e4e5-4cec-bf59-bd90ca1acc34"
}
I want to modify it (by removing a few elements) to:
{
"access_token": "<JWT Access Token>",
"token_type": "bearer",
"refresh_token": "<JWT Refresh Token>",
"expires_in": 3599,
"scope": "read write trust",
"jti": "e6a19730-e4e5-4cec-bf59-bd90ca1acc34"
}
I tried to used ResponseBodyAdvice as adviced by a few. But issue is response body object (available as public Object beforeBodyWrite(Object body ...) is of object type - "org.springframework.security.oauth2.common.DefaultOAuth2AccessToken" and not JSON. I am not sure how i can manipulate DefaultOAuth2AccessToken to remove the additional elements.
Could anybody please help me?
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
configurer
.inMemory()
.withClient(CLIEN_ID)
.secret(passwordEncoder().encode(CLIENT_SECRET))
.authorizedGrantTypes(GRANT_TYPE_PASSWORD, REFRESH_TOKEN)
.scopes(SCOPE_READ, SCOPE_WRITE, TRUST)
.accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS)
.refreshTokenValiditySeconds(REFRESH_TOKEN_VALIDITY_SECONDS);
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));
endpoints.tokenStore(tokenStore()).tokenEnhancer(tokenEnhancerChain).authenticationManager(authenticationManager);
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
}
public class CustomTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("DateOfBirth", oAuth2Authentication.getOAuth2Request().getRequestParameters().get("dob"));
additionalInfo.put("PAN_Number", oAuth2Authentication.getOAuth2Request().getRequestParameters().get("pan"));
additionalInfo.put("Address_Line_1", oAuth2Authentication.getOAuth2Request().getRequestParameters().get("addr1"));
additionalInfo.put("Address_Line_2", oAuth2Authentication.getOAuth2Request().getRequestParameters().get("addr2"));
((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(additionalInfo);
return oAuth2AccessToken;
}
}
#ControllerAdvice
public class ResponseJSONAdvice implements ResponseBodyAdvice<Object> {
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends
HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
/*
Logic to remove additional elements from response JSON.
But Object body is of type org.springframework.security.oauth2.common.DefaultOAuth2AccessToken and not JSON!!
*/
return body;
}
}

Keep using ResponseBodyAdvice, first define a class which have all field you want to show. Then make method beforeBodyWrite return that class. In beforeBodyWrite method you want to set field of defined class by value of body, then return it.
Sorry i don't good at english, ask me if u don't get it ;)
public BaseResponse beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
BaseResponse res = new BaseResponse();
res.setResponseStatusCode(StatusResponse.SUCCESS.getCode());
res.setResponseStatusMessage(StatusResponse.SUCCESS.getName());
res.setContent(body);
return res;
}

From my point of view you have to adjust your token enhance that you configure in your AuthorizationServerConfigurerAdapter
in the method
public void configure(
AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(
Arrays.asList(tokenEnhancer(), accessTokenConverter()));
endpoints.tokenStore(tokenStore())
.tokenEnhancer(tokenEnhancerChain)
.authenticationManager(authenticationManager);
}
you can set your custom enhance or configure one. This should allow you to turn on or off what is send with the token

It worked for me:
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter(), tokenEnhancer()));
endpoints.tokenStore(tokenStore())
.tokenEnhancer(tokenEnhancerChain)
.authenticationManager(authenticationManager);
}

Related

How to bind a Store using Spring Cloud Stream and Kafka?

I'd like to use a Kafka state store of type KeyValueStore in a sample application using the Kafka Binder of Spring Cloud Stream.
According to the documentation, it should be pretty simple.
This is my main class:
#SpringBootApplication
public class KafkaStreamTestApplication {
public static void main(String[] args) {
SpringApplication.run(KafkaStreamTestApplication.class, args);
}
#Bean
public BiFunction<KStream<String, String>, KeyValueStore<String,String>, KStream<String, String>> process(){
return (input,store) -> input.mapValues(v -> v.toUpperCase());
}
#Bean
public StoreBuilder myStore() {
return Stores.keyValueStoreBuilder(
Stores.persistentKeyValueStore("my-store"), Serdes.String(),
Serdes.String());
}
}
I suppose that the KeyValueStore should be passed as the second parameter of the "process" method, but the application fails to start with the message below:
Caused by: java.lang.IllegalStateException: No factory found for binding target type: org.apache.kafka.streams.state.KeyValueStore among registered factories: channelFactory,messageSourceFactory,kStreamBoundElementFactory,kTableBoundElementFactory,globalKTableBoundElementFactory
at org.springframework.cloud.stream.binding.AbstractBindableProxyFactory.getBindingTargetFactory(AbstractBindableProxyFactory.java:82) ~[spring-cloud-stream-3.0.3.RELEASE.jar:3.0.3.RELEASE]
at org.springframework.cloud.stream.binder.kafka.streams.function.KafkaStreamsBindableProxyFactory.bindInput(KafkaStreamsBindableProxyFactory.java:191) ~[spring-cloud-stream-binder-kafka-streams-3.0.3.RELEASE.jar:3.0.3.RELEASE]
at org.springframework.cloud.stream.binder.kafka.streams.function.KafkaStreamsBindableProxyFactory.afterPropertiesSet(KafkaStreamsBindableProxyFactory.java:103) ~[spring-cloud-stream-binder-kafka-streams-3.0.3.RELEASE.jar:3.0.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1855) ~[spring-beans-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1792) ~[spring-beans-5.2.5.RELEASE.jar:5.2.5.RELEASE]
I found the solution about how to use a store reading an unit test in Spring Cloud Stream.
The code below is how I applied that solution to my code.
The transformer uses the Store provided by Spring bean method "myStore"
#SpringBootApplication
public class KafkaStreamTestApplication {
public static final String MY_STORE_NAME = "my-store";
public static void main(String[] args) {
SpringApplication.run(KafkaStreamTestApplication.class, args);
}
#Bean
public Function<KStream<String, String>, KStream<String, String>> process2(){
return (input) -> input.
transformValues(() -> new MyValueTransformer(), MY_STORE_NAME);
}
#Bean
public StoreBuilder<?> myStore() {
return Stores.keyValueStoreBuilder(
Stores.persistentKeyValueStore(MY_STORE_NAME), Serdes.String(),
Serdes.String());
}
}
public class MyValueTransformer implements ValueTransformer<String, String> {
private KeyValueStore<String,String> store;
private ProcessorContext context;
#Override
public void init(ProcessorContext context) {
this.context = context;
store = (KeyValueStore<String, String>) this.context.getStateStore(KafkaStreamTestApplication.MY_STORE_NAME);
}
#Override
public String transform(String value) {
String tValue = store.get(value);
if(tValue==null) {
store.put(value, value.toUpperCase());
}
return tValue;
}
#Override
public void close() {
if(store!=null) {
store.close();
}
}
}

How to return HTTP response from HandlerMethodArgumentResolver

I have some controller's method:
#RequestMapping("/")
#AuthorizedRNUser
public Object index(UserStateVO userStateVO) {
return userStateVO;
}
Also I have HandlerMethodArgumentResolver for UserStateVO parameter
public class UserStateArgumentHandlerResovler implements HandlerMethodArgumentResolver{
#Autowired
RNService service;
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getMethod().isAnnotationPresent(AuthorizedRNUser.class) && methodParameter.getParameterType() == UserStateVO.class;
}
#Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
UserStateVO userState = service.getUserState();
if (isNull(userState))
// here i need to return 403 HTTP response
throw new RuntimeException("User is not allowed");
return userState;
}
}
And if the UserStateVO is null I need to return 403 HTTP response, but I do not know is it possible? How best to check UserStateVO and pass it into a controller or return HTTP response?
Use the same method as handling exceptions in MVC exception-handling-in-spring-mvc
Add your custom exception, e.g
public class BadRequestException extends RuntimeException {
private static final long serialVersionUID = 1L;
public BadRequestException(String message) {
super(message);
}
}
And either annotate it with #ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "User is not allowed") or add #ControllerAdvice class with method like
#ExceptionHandler(value = { BadRequestException.class })
#ResponseStatus(value = HttpStatus.FORBIDDEN)
#ResponseBody
public Map<String, String> handleBadRequestException(BadRequestException e) {
Map<String, String> retMessages = new HashMap<>();
retMessages.put("message", e.getMessage());
return retMessages;
}
what remains is just to throw it
if (isNull(userState))
// here i need to return 403 HTTP response
throw new BadRequestException("User is not allowed");

Authentication Failed: No AuthenticationProvider

I have two levels of access in the application: for everyone and only for authorized.
I'm login as a registered user,
but if I try to request data that is protected I get an error:
Authentication Failed: No AuthenticationProvider found for
com.company.security.tokenAuth.TokenAuthentication
My TokenAuthentication class:
public class TokenAuthentication extends AbstractAuthenticationToken {
private static final long serialVersionUID = -4021530026682433724L;
private UserDetails principal;
private String token;
public TokenAuthentication(String token) {
super(new HashSet<>());
this.token = token;
}
public TokenAuthentication(String token, Collection<? extends GrantedAuthority> authorities,
boolean isAuthenticated, UserDetails principal) {
super(authorities);
this.principal = principal;
this.setAuthenticated(isAuthenticated);
}
#Override
public Object getCredentials() {
return null;
}
#Override
public UserDetails getPrincipal() {
return principal;
}
public String getToken() {
return token;
}
}
My TokenAuthenticationProvider class:
#Component
public class TokenAuthenticationProvider implements AuthenticationProvider {
private TokenService tokenService;
private AccountDetailsService accountService;
public TokenAuthenticationProvider(TokenService tokenService, AccountDetailsService accountService) {
this.tokenService = tokenService;
this.accountService = accountService;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication instanceof TokenAuthentication) {
return processAuthentication((TokenAuthentication) authentication);
} else {
authentication.setAuthenticated(false);
return authentication;
}
}
#Override
public boolean supports(Class<?> aClass) {
return aClass.equals(TokenAuthentication.class);
}
private TokenAuthentication processAuthentication(TokenAuthentication authentication) {
try {
Account token = tokenService.parseToken(authentication.getToken());
Set<GrantedAuthority> authorities = new HashSet<>();
authorities.add(new SimpleGrantedAuthority(token.getRole().name()));
return new TokenAuthentication(authentication.getToken(), authorities,
true, new AccountDetails((Account) accountService.loadUserByUsername(token.getEmail())));
} catch (ValidationException e) {
throw new AuthenticationServiceException("Invalid token");
} catch (Exception e) {
throw new AuthenticationServiceException("Token corrupted");
}
}
}
What is my problem?
Thank you for your help.
I found the answer.
I changed my authentication according to the project by reference https://github.com/oharsta/spring-jwt/tree/50f130ee5d63d746cc9d7adf2f0d8f085327a84a
And fixed role, since I have only one user and one role in the form of the enum. And during authentication, the list of roles is used.
After solving this problem, everything worked.

How to get custom UserDetailService Object in Resource Server in spring-security-oauth2?

I have separate authorization server and resource server.
Authorization server is pointing to a separate database. I haves used CustomUserDetailService for user related information.
I have used CustomTokenEnhancer to have additional information apart from the token in the response.
#Configuration
public class OAuth2Configuration {
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter implements EnvironmentAware {
private static final String ENV_OAUTH = "authentication.oauth.";
private static final String PROP_CLIENTID = "clientid";
private static final String PROP_SECRET = "secret";
private static final String PROP_TOKEN_VALIDITY_SECONDS = "tokenValidityInSeconds";
private RelaxedPropertyResolver propertyResolver;
#Autowired
private DataSource dataSource;
#Autowired
private CustomUserDetailService userDetailsService;
#Bean
public TokenStore tokenStore() {
return new CustomTokenStore(dataSource);
}
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints
.tokenStore(tokenStore())
.userDetailsService(userDetailsService)
.tokenEnhancer(tokenEnhancer())
.accessTokenConverter(accessTokenConverter())
.authenticationManager(authenticationManager);
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
#Bean
public DefaultAccessTokenConverter accessTokenConverter() {
return new DefaultAccessTokenConverter();
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
}
}
CustomUserDetailService Class:
#Service
public class CustomUserDetailService implements UserDetailsService {
#Autowired
private AccountRepository accountRepository;
#Override
public UserDetails loadUserByUsername(String username) {
Account account = accountRepository.getByEmail(username);
if(account == null) {
throw new UsernameNotFoundException(username);
}
return new MyUserPrincipal(account);
}
}
CustomTokenEnhancer Class:
public class CustomTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
MyUserPrincipal user = (MyUserPrincipal) authentication.getPrincipal();
final Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("user_information", user.getAccount());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
Request/Response
http://localhost:9191/authserver/oauth/token
{
"access_token": "fddb571e-224e-4cd7-974e-65104dd24b41",
"token_type": "bearer",
"refresh_token": "eb412b00-9e4e-4d6c-86d8-324d999b5f08",
"expires_in": 100,
"scope": "read write",
"account_information": {
"id": 14,
"firstname": "name",
"lastname": "lastname",
}
}
At resource server side, I have used RemoteTokenSerice to verify the the token presented by user is valid or not.
#Configuration
#EnableResourceServer
public class OAuthResourceConfig extends ResourceServerConfigurerAdapter {
private TokenExtractor tokenExtractor = new BearerTokenExtractor();
#Override
public void configure(HttpSecurity http) throws Exception {
http.addFilterAfter(new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (tokenExtractor.extract(request) == null) {
SecurityContextHolder.clearContext();
}
filterChain.doFilter(request, response);
}
}, AbstractPreAuthenticatedProcessingFilter.class);
http.csrf().disable();
http.authorizeRequests().anyRequest().authenticated();
}
#Bean
public AccessTokenConverter accessTokenConverter() {
return new DefaultAccessTokenConverter();
}
#Bean
#Primary
public RemoteTokenServices remoteTokenServices(final #Value("${auth.server.url}") String checkTokenUrl,
final #Value("${auth.server.clientId}") String clientId,
final #Value("${auth.server.clientsecret}") String clientSecret) {
final RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
remoteTokenServices.setCheckTokenEndpointUrl(checkTokenUrl+"?name=value");
remoteTokenServices.setClientId(clientId);
remoteTokenServices.setClientSecret(clientSecret);
remoteTokenServices.setAccessTokenConverter(accessTokenConverter());
return remoteTokenServices;
}
}
So It is working properly and when I make a request to resource server with token, it processes the request if the token is valid. My question is I want to get Account object in resource server. I tried with below:
Account account = (Account)SecurityContextHolder.getContext().getAuthentication().getPrincipal()
But it gives string and not the complete user define object and hence it throws the exception.How to get Account object in any controller in Resource server?
{
"timestamp": 1499334657703,
"status": 500,
"error": "Internal Server Error",
"exception": "java.lang.ClassCastException",
"message": "java.lang.String cannot be cast to Account",
"path": "/secure"
}
I tried with link but is it possible to inject two token services, RemoteTokenService and CustomUserInfoTokenServices both?
Also I think here spring makes internal call from Resource Server to Authorization Server (http://localhost:9191/authserver/oauth/check_token?token=d8dae984-7bd8-4aab-9990-a2c916dfe667) to validate the token.
Is there any way I can get those information in controller without calling this endpoint again.
Response:
{
"exp": 1499333294,
"account_information": {
"accountid": 14,
"firstname": "fname",
"lastname": "lname",
},
"user_name": "abc#abc.com",
"client_id": "clientId",
"scope": [
"read",
"write"
]
}
I have overridden below method and added some logic.
public class CustomAccessTokenConverter extends DefaultAccessTokenConverter{
private UserAuthenticationConverter userTokenConverter = new DefaultUserAuthenticationConverter();
#Override
public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
Map<String, String> parameters = new HashMap<String, String>();
#SuppressWarnings("unchecked")
Set<String> scope = new LinkedHashSet<String>(map.containsKey(SCOPE) ? (Collection<String>) map.get(SCOPE)
: Collections.<String>emptySet());
Authentication user = userTokenConverter.extractAuthentication(map);
String clientId = (String) map.get(CLIENT_ID);
parameters.put(CLIENT_ID, clientId);
parameters.put("account_information", String.valueOf((((Map) map.get("account_information")).get("accountid"))));
#SuppressWarnings("unchecked")
Set<String> resourceIds = new LinkedHashSet<String>(map.containsKey(AUD) ? (Collection<String>) map.get(AUD)
: Collections.<String>emptySet());
Map<String, Serializable> extensions = new HashMap<String, Serializable>();
extensions.put("account_information", (HashMap) map.get("account_information"));
OAuth2Request request = new OAuth2Request(parameters, clientId, null, true, scope, resourceIds, null, null,
extensions);
return new OAuth2Authentication(request, user);
}
}
Resource Server Class
#Bean
public AccessTokenConverter accessTokenConverter() {
//return new DefaultAccessTokenConverter();
return new CustomAccessTokenConverter();
}
#Bean
#Primary
public RemoteTokenServices remoteTokenServices(final #Value("${auth.server.url}") String checkTokenUrl,
final #Value("${auth.server.clientId}") String clientId,
final #Value("${auth.server.clientsecret}") String clientSecret) {
final RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
remoteTokenServices.setCheckTokenEndpointUrl(checkTokenUrl+"?name=value");
remoteTokenServices.setClientId(clientId);
remoteTokenServices.setClientSecret(clientSecret);
remoteTokenServices.setAccessTokenConverter(accessTokenConverter());
return remoteTokenServices;
}
Now I can get additional information in controller.
OAuth2Authentication authentication = (OAuth2Authentication)SecurityContextHolder.getContext().getAuthentication();
Map<String, Serializable> map = authentication.getOAuth2Request().getExtensions();
I use in this way.
after call /oauth/token, I can get below, and member_id is additional field I added.
{
"access_token": "this is access token",
"token_type": "bearer",
"refresh_token": this is refreshtoken",
"expires_in": 3599,
"scope": "web",
"member_id": "d2lsbGlhbQ",
"jti": "79b9b523-921d-45c1-ba97-d3565f1d68b7"
}
after decode the access token, I can see this custom field member_id in it.
below are what I do in my Resource Server.
Declear Bean DefaultTokenService in configuration class
#Bean
#Primary
public DefaultTokenServices tokenServices() throws IOException {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
return defaultTokenServices;
}
here I inject resource into my controller.
#Autowired
private ResourceServerTokenServices resourceServerTokenServices;
#GetMapping("/addition")
public Map<String, Object> addition() {
Map<String, Object> response = new HashMap<>();
response.put("member_id", resourceServerTokenServices.readAccessToken(((OAuth2AuthenticationDetails) SecurityContextHolder.getContext().getAuthentication().getDetails()).getTokenValue()).getAdditionalInformation().get("member_id"));
return response;
}
then I call this /addition, I can see the response.
{
"member_id": "d2lsbGlhbQ"
}
I'm a newer to oAuth2 with JWT, so I have do some research on internet, but cannot find a sensitive method to get it from resource server. so I try some method to get this. hope it works.

User can't be cast to to com.example.security.CustomUserDetails Spring Security

I'm trying to set my user on to a course object that I created and I keeping getting the error org.springframework.security.core.userdetails.User cannot be cast to com.example.security.CustomUserDetails I know I'm getting the user details from the session, because when I run it in debug mode I can see the name of the currently logged in user, so I think I'm somewhat close to a solution.
Here's my Controller
#RequestMapping(value="createCourse", method=RequestMethod.POST)
public String createCoursePost (#ModelAttribute Course course, Long userId, ModelMap model, Authentication auth)
{
CustomUserDetails myUserDetails = (CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
userId = myUserDetails.getUser().getId();
User user = userRepo.findOne(userId);
course.setUser(user);
courseRepo.save(course);
return "redirect:/courses";
}
Here's my UserDetailsServiceImpl
#Service
#Qualifier("customUserDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private UserRepository userRepo;
#Transactional
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
com.example.domain.User user = userRepo.findByUsername(username);
List<GrantedAuthority> authorities = buildUserAuthority(user.getRoles());
return buildUserForAuthentication(user, authorities);
}
private User buildUserForAuthentication(com.example.domain.User user,
List<GrantedAuthority> authorities) {
return new User(user.getUsername(), user.getPassword(), authorities);
}
private List<GrantedAuthority> buildUserAuthority(Set<UserRole> userRoles) {
Set<GrantedAuthority> setAuths = new HashSet<GrantedAuthority>();
// Build user's authorities
for (UserRole userRole : userRoles) {
setAuths.add(new SimpleGrantedAuthority(userRole.getRoleName()));
}
return new ArrayList<GrantedAuthority>(setAuths);
}
Here's my customUserDetails, it's possible that me getting and setting the user could be redundant, but I saw an example with this, and so I'm not really sure what to do with this.
public class CustomUserDetails extends User implements UserDetails{
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
private static final long serialVersionUID = 2020921373107176828L;
public CustomUserDetails () {}
public CustomUserDetails (User user) {
super(user);
}
#Override
public Set<Authorities> getAuthorities() {
return super.getAuthorities();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
Here's my webSecurityConfig
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static PasswordEncoder encoder;
#Autowired
private UserDetailsService customUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.csrfTokenRepository(csrfTokenRepository());
http
.authorizeRequests()
.antMatchers("/", "/home", "/register", "/courses", "/editCourse", "/sets", "/search", "/viewCourse/{courseId}", "/fonts/glyphicons-halflings-regular.ttf","fonts/glyphicons-halflings-regular.woff", "fonts/glyphicons-halflings-regular.woff", "/viewCourse/post/{postId}").permitAll()
.anyRequest().authenticated();
http
.formLogin()
.loginPage("/login")
.usernameParameter("username").passwordParameter("password")
.permitAll()
.and()
.logout()
.permitAll()
.logoutSuccessUrl("/loggedout")
.and()
.sessionManagement()
.maximumSessions(1);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
if(encoder == null) {
encoder = new BCryptPasswordEncoder();
}
return encoder;
}
private CsrfTokenRepository csrfTokenRepository()
{
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setSessionAttributeName("_csrf");
return repository;
}
}
I solved this just in case anyone else sees this. I think my problem was that I wasn't returning userdetails in the userdetailsimpl class. And I also had to update my controller. So here's my new code
Controller
#RequestMapping(value="createCourse", method=RequestMethod.POST)
public String createCoursePost (#ModelAttribute Course course, Long userId, ModelMap model)
{
CustomUserDetails myUserDetails = (CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
userId = myUserDetails.getUser().getId();
User user = userRepo.findOne(userId);
course.setUser(user);
courseRepo.save(course);
return "redirect:/courses";
}
And userDetailsServiceImpl
#Service
#Qualifier("customUserDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private UserRepository userRepo;
#Transactional
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
com.example.domain.User user = userRepo.findByUsername(username);
CustomUserDetails customUserDetails = new CustomUserDetails(user);
customUserDetails.setUser(user);
return customUserDetails;
}
}

Resources