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.
Related
I am Working on a spring boot project for an e-commerce website, As a beginner, I try to add spring security in it so the problem is when I try to test my rest login API using postman I have a status code 200 and the body is always the default login page of spring security. I will be thankful for any advice or any solution.
Here is my user class :
public class User implements Serializable {
private static final long serialVersionUID = -2800960695811489984L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String firstName;
private String lastName;
#Column(nullable = false, unique = true)
private String username;
#Column(nullable = false)
private String address;
#Column(nullable = false, unique = true)
private String email;
private String password;
private boolean isEnabled;
#Column(name = "role" , nullable = false)
#Enumerated(EnumType.STRING)
private Role role;
Here is my Role enum :
public enum Role {
USER ,ADMIN
}
MyUserDetails Class :
public class MyUserDetails implements UserDetails {
String ROLE_PREFIX ="ROLE_";
private String email;
private String password;
private boolean active;
private Role role;
public MyUserDetails(User user) {
super();
this.email = user.getEmail();
this.password = user.getPassword();
this.active = user.isEnabled();
this.role = role;
}
public MyUserDetails(String email, String password, boolean enabled, Role role) {
super();
}
public static MyUserDetails create(User user) {
return new MyUserDetails(user.getEmail(), user.getPassword() ,user.isEnabled(), user.getRole());
}
Here is MyUserDetailsService :
#Service
#ToString
public class MyUserDetailsService implements UserDetailsService {
UserRepository userRepository;
#Autowired
public MyUserDetailsService(UserRepository userRepository) {
super();
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
if (email == null || email.isEmpty()) {
throw new UsernameNotFoundException("email is Empty");
}
User user = userRepository.findByEmail(email);
if (user != null) {
return user.toCurrentUserDetails();
}
throw new UsernameNotFoundException( email + "is not found !!!");
}
}
Here is my RestController :
#CrossOrigin(origins = "*")
#RestController
#RequestMapping("/home")
public class HomeController {
private AuthenticationManager authenticationManager;
private MyUserDetailsService userDetailsService;
private UserRepository userRepository;
private Jwt jwtUtil;
#Autowired
public HomeController(AuthenticationManager authenticationManager, MyUserDetailsService userDetailsService
, UserRepository userRepository, Jwt jwtUtil) {
this.authenticationManager = authenticationManager;
this.userDetailsService = userDetailsService;
this.userRepository = userRepository;
this.jwtUtil = jwtUtil;
}
#PostMapping("/signin")
public ResponseEntity<ServerResp> addUser(#RequestBody User user) {
ServerResp response = new ServerResp();
try {
if (Validator.isUserEmpty(user)) {
response.setStatus(ResponseCode.BAD_REQUEST_CODE);
response.setMessage(ResponseCode.BAD_REQUEST_MESSAGE);
} else if (!Validator.isValidEmail(user.getEmail())) {
response.setStatus(ResponseCode.BAD_REQUEST_CODE);
response.setMessage(ResponseCode.INVALID_EMAIL_FAIL_MSG);
} else {
user.setRole(Role.USER);
user.setEnabled(true);
User reg = userRepository.save(user);
response.setStatus(ResponseCode.SUCCESS_CODE);
response.setMessage(ResponseCode.CUST_REG);
}
} catch (Exception e) {
response.setStatus(ResponseCode.FAILURE_CODE);
response.setMessage(e.getMessage());
}
return new ResponseEntity<ServerResp>(response, HttpStatus.ACCEPTED);
}
#PostMapping("/login")
public ResponseEntity<ServerResp> authentification(#RequestBody HashMap<String, String> credential) {
final String email = credential.get(WebConstants.USER_EMAIL);
final String password = credential.get(WebConstants.USER_PASSWORD);
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(email, password));
} catch (BadCredentialsException e) {
throw new UserNotFoundException(email);
}
final UserDetails userDetails = userDetailsService.loadUserByUsername(email);
final String jwt = jwtUtil.generateToken(userDetails);
ServerResp resp = new ServerResp();
resp.setStatus(ResponseCode.SUCCESS_CODE);
resp.setMessage(ResponseCode.SUCCESS_MESSAGE);
resp.setAUTH_TOKEN(jwt);
return new ResponseEntity<ServerResp>(resp, HttpStatus.OK);
}
}
Here is my Security Configuration Class :
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private MyUserDetailsService userDetailsService;
private JwtFilter jwtFilter;
#Autowired
DataSource datasource;
#Autowired
public SecurityConfiguration(MyUserDetailsService userDetailsService, JwtFilter jwtFilter) {
this.userDetailsService = userDetailsService;
this.jwtFilter = jwtFilter;
}
public SecurityConfiguration(boolean disableDefaults, MyUserDetailsService userDetailsService, JwtFilter jwtFilter) {
super(disableDefaults);
this.userDetailsService = userDetailsService;
this.jwtFilter = jwtFilter;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers("/resources/**", "/static/**", "/public/**").permitAll()
.antMatchers("/home/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**")
.hasRole("USER")
.anyRequest().authenticated().and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http
.formLogin()
//.loginPage("/home/login")
.usernameParameter("email")
.passwordParameter("password")
.permitAll()
.and()
.logout()
.permitAll();
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
// TODO Auto-generated method stub
return super.authenticationManagerBean();
}
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
Here is my user in the database ( MySQL):
user in database
And finally, this is the result of the test in postman :
Test in Postman
Try sending your credentials using Authorization tab in postman, select authorization type to basic Auth.
I do the following steps with my Spring Boot app that has Spring Security. Note than my set up is most likely different from yours but the key here is the csfr token.
These are the steps I am following to do the login in Postman:
Start my Server
Open a chrome tab and go to developer tools and then the Network tab.
In the Network tab, I go to the DOC tab.
I then enter the url: http://localhost:9005/mywarehouse and press enter
This takes me to the Login Form where I then click on Create User
After the user is created I am taken back to the login form and login with the new user credentials.
In Postman, I submit a GET request such as http://localhost:9005/mywarehouse/products which returns the HTML of my login form instead of JSON.
Now back in chrome developer tab on the Doc tab I go to the last entry which is my login and I right click on it and select "Copy as cURL (bash)"
I then go back into Postman and click on the Import button and select "raw text" and then paste.
I then click on Continue and then Import. This opens up a new tab with a POST request and I click on send.
This returns HTML code. I locate the csfr token and copy it and then go to the Body tab and replace the csfr token that is currently there with the one returned from the HTML.
I then resend the POST request created by the Import and then I go back to my original GET request tab and resend and this time I get the JSON response I was expecting.
I have faced this same issue and I got a solution.
In postman go to the settings --> In General section --> turn off the automatically follow redirects.
I am testing web application using Junit 5, every thing working fine except
SecurityContextHolder.getContext().getAuthentication().getPrincipal()
line in controller class while running test case above code of line return object of
org.springframework.security.core.userdetails.User instead of MyUserDetails (While running application in spring boot return object is MyUserDetails and code works well)
My Test case class
#ExtendWith(SpringExtension.class)
#WebMvcTest(VendorController.class)
#AutoConfigureMockMvc
class VendorControllerTest {
#Autowired
private MockMvc mvc;
#Test
void test() throws Exception {
mvc.perform(MockMvcRequestBuilders.post("/vendor/user/getVendorList").with(csrf().asHeader())
.param("iDisplayStart","0")
.param("iDisplayLength", "10")
.param("iSortCol_0", "0")
.param("sSortDir_0", "asc")
.param("sSearch","")
.with(user("username").password("password").roles("2"))).andExpect(status().isOk());
}
}
What code I am missing so that it is return org.springframework.security.core.userdetails.User object while running test case using Junit 5 but working properly if I run project through spring boot.
Adding UserDetailsService implementation code
#Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
#Autowired
private UserSvc userSvc;
#Transactional(readOnly=true)
#Override
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {
com.esociety.entity.UserEntity userEntity = userSvc.findByUserName(username);
List<GrantedAuthority> authorities = buildUserAuthority(userEntity.getUserType());
return buildUserForAuthentication(userEntity, authorities);
}
private User buildUserForAuthentication(com.esociety.entity.UserEntity userEntity, List<GrantedAuthority> authorities) {
MyUserDetails myUserDetails = new MyUserDetails (userEntity.getUsername(), userEntity.getPassword(), userEntity.isEnabled(), userEntity.isAccountNonExpired(), userEntity.isCredentialsNonExpired(),userEntity.isAccountNonLocked(),authorities,userEntity.getUserId(),userEntity.getSocietyId(),userEntity.getUserType(),userEntity.getFirstName(),userEntity.getMiddleName(),userEntity.getLastName());
return myUserDetails;
}
private List<GrantedAuthority> buildUserAuthority(String userType) {
Set<GrantedAuthority> setAuths = new HashSet<GrantedAuthority>();
// Build user's authorities
setAuths.add(new SimpleGrantedAuthority(userType));
List<GrantedAuthority> Result = new ArrayList<GrantedAuthority>(setAuths);
return Result;
}
}
MyUserDetails class
public class MyUserDetails extends User {
public MyUserDetails(String username, String password, boolean enabled,
boolean accountNonExpired, boolean credentialsNonExpired,
boolean accountNonLocked,
Collection<? extends GrantedAuthority> authorities, Long userId,
Long societyId, String userType, String firstName,
String middleName, String lastName) {
super(username, password, enabled, accountNonExpired,
credentialsNonExpired, accountNonLocked, authorities);
this.userId = userId;
this.societyId = societyId;
this.userType = userType;
this.firstName = firstName;
this.middleName = middleName;
this.lastName = lastName;
}
// getter setter goes here
}
The problem is you are using SecurityMockMvcRequestPostProcessors.user(String username) RequestPostProcessor which will create and return a org.springframework.security.core.userdetails.User which is an implementation of UserDetails but not the MyUserDetails that you want.
Instead you can use SecurityMockMvcRequestPostProcessors.user(UserDetails user) which allows to submit your own implementation of UserDetails, in this case MyUserDetails that you want.
Change your test as following:
#Test
void test() throws Exception {
mvc.perform(MockMvcRequestBuilders.post("/vendor/user/getVendorList").with(csrf().asHeader())
.param("iDisplayStart","0")
.param("iDisplayLength", "10")
.param("iSortCol_0", "0")
.param("sSortDir_0", "asc")
.param("sSearch","")
.with(user(new MyUserDetails ("username", "password", true, true, true,true, Arrays.asList(new SimpleGrantedAuthority("2")),1,1,USER_TYPE,"FirstName","MiddleName","LastName")))).andExpect(status().isOk());
}
Reference Spring Security Source code
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.
I need to create a POJO class that will store information pertaining to a user. In spring security, my authorities table has an extra column of instituionIds, it is a CSV string that will be needed in various DAO calls. I need to set the values of this class, which will be needed when querying the database.
#Component
#Scope("session")
public class InstitutionList {
private String institutionList = "";
public String getInstitutionList() {
return institutionList;
}
public void setInstitutionList(String institutionList) {
this.institutionList = institutionList;
}
}
I need to use this in my custom UserDetailsService implementation
#Transactional
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
private static final Logger logger = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
private #Autowired ACSAdminUsersService acsAdminUsersService;
private #Autowired ACSAdminAuthoritiesService acsAdminAuthoritiesService;
private String[] authority;
public ACSAdminUsers getUserByAdminUsername(String username) {
logger.info("Getting user by username");
ACSAdminUsers user = acsAdminUsersService.getUserByAdminUsername(username);
if(user!=null) acsAdminUsersService.addLogInInfo(username);
return user;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
ACSAdminUsers user = getUserByAdminUsername(username);
logger.info("Username is : " + username);
logger.info("user is : " + user);
authority = acsAdminAuthoritiesService.getAuthoritiesForRole(user.getRole());
logger.info("User role is : " + authority);
if(authority == null) {
throw new UsernameNotFoundException("User : "+username+" has no authorities." );
}else {
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
return new org.springframework.security.core.userdetails.User(username,user.getAdmin_pass(),
true,accountNonExpired,credentialsNonExpired,
accountNonLocked,
getAuthorities(username));
}
}
public Collection<? extends GrantedAuthority> getAuthorities(String username) {
List<GrantedAuthority> authList = null;
authList=new ArrayList<GrantedAuthority>();
for(int i = 0; i < authority.length; i++) {
SimpleGrantedAuthority s = new SimpleGrantedAuthority(this.authority[i]);
authList.add(s);
}
return authList;
}
}
In the above java class I need to query the database and fetch, along with authorities, institutionIds which need to be used in queries throughout the application.
You could create your own InstitutionContextHolder and use a threadlocal to store the object. In this way you can acces this object in every class you want. You can take a look at the SecurityContextHolder for examples.
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;
}
}