I'm currently developing a fullstack web application with a React Frontend and Spring Boot backend. I've implemented Spring security and JWT for authentication, but I can't access my API endpoints (see Controller) ever since. I've managed to access the GET request endpoints, but none of the PUT or DELETE requests seem to work despite logging in on the backend before starting a request.
I've seen that disabling csrf solved the problem in another post, but I've never enabled it anyway, so that wouldn't do the trick for me.
WebSecurityConfig file:
#Configuration
#AllArgsConstructor
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/v*/registration/**")
.permitAll()
.anyRequest()
.authenticated().and()
.formLogin();
}
Controller (REST API)
#RestController
#RequestMapping(path = "/question")
#CrossOrigin("*")
public class QuestionController {
private final QuestionService questionService;
#Autowired
public QuestionController(QuestionService questionService) {
this.questionService = questionService;
}
#CrossOrigin("*")
#GetMapping("/all")
public ResponseEntity<List<Question>> getAllQuestions() {
List<Question> questions = questionService.findAllQuestions();
return new ResponseEntity<>(questions, HttpStatus.OK);
}
#CrossOrigin("*")
#GetMapping("/find/{id}")
public ResponseEntity<Question> getQuestionById(#PathVariable("id") Long id) {
Question question = questionService.findQuestionById(id);
return new ResponseEntity<>(question, HttpStatus.OK);
}
#CrossOrigin("*")
#PostMapping("/add")
public ResponseEntity<Question> addQuestion(#RequestBody Question question) {
Question newQuestion = questionService.addQuestion(question);
return new ResponseEntity<>(newQuestion, HttpStatus.CREATED);
}
#CrossOrigin("*")
#PutMapping("/update/{id}")
public ResponseEntity<Question> updateQuestion(#RequestBody Question question) {
Question updateQuestion = questionService.updateQuestion(question);
return new ResponseEntity<>(updateQuestion, HttpStatus.OK);
}
#CrossOrigin("*")
#DeleteMapping("(/delete/{id}")
public ResponseEntity<Question> deleteQuestion(#PathVariable("id") Long id) {
questionService.deleteQuestion(id);
return new ResponseEntity<>(HttpStatus.OK);
}
}
Example for the GET request endpoint that works:
Example for the DELETE request endpoint that doesn't work:
Edit: This is the Code for implementing UserDetailsService
#Service
#Autowired can be left out by using this annotation.
#AllArgsConstructor
public class BenutzerkontoService implements UserDetailsService {
private final static String USER_NOT_FOUND_MSG = "User with email %s not found";
private final BenutzerkontoRepository benutzerkontoRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
private final ConfirmationTokenService confirmationTokenService;
public List<Benutzerkonto> findAllBenutzerkonto() {
// findAll() returns a list of all user objects
return benutzerkontoRepository.findAll();
}
/**
* This method is responsible for identifying the given email inside the database.
*
* #param email
* #return
* #throws UsernameNotFoundException
*/
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return benutzerkontoRepository.findByEmail(email).orElseThrow(() -> new UsernameNotFoundException(String.format(USER_NOT_FOUND_MSG, email)));
}
/**
* The following function checks, whether the user already exists (by email) and registers the user with an
* encoded password, if the email address does not exist already.
*
* The user also gets a random JSON Web Token assigned
*
* #param benutzerkonto
* #return
*/
public String signUpUser(Benutzerkonto benutzerkonto) {
// Check whether user exists
boolean userExists = benutzerkontoRepository.findByEmail(benutzerkonto.getEmail()).isPresent();
if (userExists) {
throw new IllegalStateException("Email is already taken");
}
// Encode the user password
String encodedPassword = bCryptPasswordEncoder.encode(benutzerkonto.getPassword());
// Replace the plain text password with the encoded version
benutzerkonto.setPasswort(encodedPassword);
// Save user to database
benutzerkontoRepository.save(benutzerkonto);
// Create random String via the UUID class for using it as token
String token = UUID.randomUUID().toString();
// Instantiate ConfirmationToken class, which defines the token for account confirmation
ConfirmationToken confirmationToken = new ConfirmationToken(
token,
LocalDateTime.now(),
// Make token invalid after 15 minutes
LocalDateTime.now().plusMinutes(15),
benutzerkonto
);
// Save token to database
// TODO: Shouldn't it be saved by a confirmationTokenRepository object? Why does this also work?
confirmationTokenService.saveConfirmationToken(confirmationToken);
return token;
}
/**
* This function takes the email address as a parameter and enables/activates the email for logging in.
*
* #param email
* #return
*/
public int enableAppUser(String email) {
return benutzerkontoRepository.enableAppUser(email);
}
/**
* This method adds a new user account to the database, but it searches for the passed value of email
* inside the database first. The user object "benutzerkonto" will only be saved in the database repository,
* if the email does not exist already.
*
* #param benutzerkonto
*/
public void addNewUser(Benutzerkonto benutzerkonto) {
// userEmailPresence can be null, if the email does not exist in the database yet, which is why it's an Optional.
Optional<Benutzerkonto> userEmailPresence = benutzerkontoRepository.findBenutzerkontoByEmail(benutzerkonto.getUsername());
if (userEmailPresence.isPresent()) {
throw new IllegalStateException("Email already taken.");
} else {
benutzerkontoRepository.save(benutzerkonto);
}
}
}
Edit2: This is the user class
#Getter
#Setter
#EqualsAndHashCode
#NoArgsConstructor
#Entity
#Table
public class Benutzerkonto implements Serializable, UserDetails {
#SequenceGenerator(
name = "student_sequence",
sequenceName = "student_sequence",
allocationSize = 1
)
#Id
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "student_sequence"
)
#Column(nullable = false)
private Long id;
private String email;
private String passwort;
#Column(nullable = false, updatable = false)
#Enumerated(EnumType.STRING)
private UserRole rolle;
private Boolean locked = false;
// false by default, because user has to confirm via email first
private Boolean enabled = false;
// Constructor
public Benutzerkonto(String email, String passwort, UserRole rolle) {
this.email = email;
this.passwort = passwort;
this.rolle = rolle;
}
#Override
public String toString() {
return "Benutzerkonto{" +
"id=" + id +
", email='" + email + '\'' +
", passwort='" + passwort + '\'' +
", rolle=" + rolle +
", locked=" + locked +
", enabled=" + enabled +
'}';
}
// Methods of UserDetails interface
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(rolle.name());
return Collections.singletonList(authority);
}
#Override
public String getPassword() {
return passwort;
}
#Override
public String getUsername() {
return email;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return !locked;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return enabled;
}
}
So apart from requests coming to /api/v*/registration/** others are secured. What does that mean?, it means until you have authorized users having authorized roles cannot access any other endpoint. So you need to do some things like:
implement UserDetails of package org.springframework.security.core.userdetails and implement the method:
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles == null?null:roles.stream().map(m->new SimpleGrantedAuthority(m.getAuthority())).collect(Collectors.toSet());
}
Add roles to your entity class:
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.PERSIST)
#JoinTable(
name = "user_role",
joinColumns = #JoinColumn(
name = "user_id",
referencedColumnName = "id"
),
inverseJoinColumns = #JoinColumn(
name = "role_id",
referencedColumnName = "id"
))
private List<Role> roles;
Use those roles in your endpoint:
#PreAuthorize(hasRole('ROLE_role_name'))
#GetMapping(path = EndPoint.PATIENT_HOME, consumes = "application/json", produces = "application/json")
public ResponseEntity<YourDTO> Home(Principal principal) {
return new ResponseEntity<YourDTO>(yourDTO, HttpStatus.OK);
}
Related
I have the following two classes which provide the JWT authentication mechanisem.
CustomDelegatingAuthenticationProvider
#Singleton
#Replaces(value = DelegatingAuthenticationProvider.class)
public class CustomDelegatingAuthenticationProvider extends DelegatingAuthenticationProvider {
/**
* #param userFetcher Fetches users from persistence
* #param passwordEncoder Collaborator which checks if a raw password matches an encoded password
* #param authoritiesFetcher Fetches authorities for a particular user
*/
public CustomDelegatingAuthenticationProvider(UserFetcher userFetcher, PasswordEncoder passwordEncoder, AuthoritiesFetcher authoritiesFetcher) {
super(userFetcher, passwordEncoder, authoritiesFetcher);
}
#Override
protected Publisher<AuthenticationResponse> createSuccessfulAuthenticationResponse(AuthenticationRequest authenticationRequest, UserState userState) {
if (userState instanceof UserMember) {
UserMember user = (UserMember) userState;
return Flowable
.fromPublisher(authoritiesFetcher.findAuthoritiesByUsername(user.getUsername()))
.map(authorities -> new HDSUser(user.getUsername(), authorities, user.getId()));
}
return super.createSuccessfulAuthenticationResponse(authenticationRequest, userState);
}
}
CustomJWTClaimsSetGenerator
#Singleton
#Replaces(value = JWTClaimsSetGenerator.class)
public class CustomJWTClaimsSetGenerator extends JWTClaimsSetGenerator {
CustomJWTClaimsSetGenerator(TokenConfiguration tokenConfiguration, #Nullable JwtIdGenerator jwtIdGenerator, #Nullable ClaimsAudienceProvider claimsAudienceProvider) {
super(tokenConfiguration, jwtIdGenerator, claimsAudienceProvider);
}
protected void populateWithUserDetails(JWTClaimsSet.Builder builder, UserDetails userDetails) {
super.populateWithUserDetails(builder, userDetails);
if (userDetails instanceof HDSUser) {
builder.claim("userId", ((HDSUser) userDetails).getId());
}
}
}
The default response to the client looks like this:
My question. How can I extend the class to return all user attributes? Besides username I want to have the user id.
UPDATE
HDS user class which gathers the DB id
#CompileStatic
public class HDSUser extends UserDetails {
private long id;
public HDSUser(String username, Collection<String> roles, long id) {
super(username, roles);
this.id = id;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
}
To extend the returned data you need to extend (implement custom) TokenRenderer as well as a custom version of the AccessRefreshToken.
Just as a simple example see the following code snipped which will extend the default access token payload with userId field.
First, create a custom AccessRefreshToken class with additional fields which are required.
#Introspected
#Getter
#Setter
public class CustomAccessRefreshToken extends BearerAccessRefreshToken {
// the new field which will be in the response
private String userId;
public CustomAccessRefreshToken(String username,
Collection<String> roles,
Integer expiresIn,
String accessToken,
String refreshToken,
String tokenType
) {
super(username, roles, expiresIn, accessToken, refreshToken, tokenType);
}
}
Next, we will need a TokenRenderer which will be used by the underlying subsystem to generate our custom token.
#Singleton
#Replaces(value = BearerTokenRenderer.class)
public class CustomTokenRenderer implements TokenRenderer {
private static final String BEARER_TOKEN_TYPE = HttpHeaderValues.AUTHORIZATION_PREFIX_BEARER;
#Override
public AccessRefreshToken render(Integer expiresIn, String accessToken, #Nullable String refreshToken) {
return new AccessRefreshToken(accessToken, refreshToken, BEARER_TOKEN_TYPE, expiresIn);
}
#Override
public AccessRefreshToken render(Authentication authentication, Integer expiresIn, String accessToken, #Nullable String refreshToken) {
CustomAccessRefreshToken token = new CustomAccessRefreshToken(authentication.getName(), authentication.getRoles(), expiresIn, accessToken, refreshToken, BEARER_TOKEN_TYPE);
// here just take the user data from Authentication object or access any other service
token.setUserId("Some user id");
return token;
}
}
That's it )) Just implement render() method the way you want and add as many custom fields as needed.
The response from the given example will look like
{
"username": "sherlock",
"userId": "Some user id",
"access_token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzaGVybG9jayIsIm5iZiI6MTYzNjk5MTgzMSwicm9sZXMiOltdLCJpc3MiOiJtaWNyb25hdXRndWlkZSIsImV4cCI6MTYzNjk5NTQzMSwiaWF0IjoxNjM2OTkxODMxfQ.Cat1CTsUZkCj-OHGafiefNm1snPsALoaNw9y2xwF5Pw",
"token_type": "Bearer",
"expires_in": 3600
}
If you are on the older version of the Micronaut v1.x the TokenRenderer will look like this.
#Singleton
#Replaces(value = BearerTokenRenderer.class)
public class CustomTokenRenderer implements TokenRenderer {
private static final String BEARER_TOKEN_TYPE = HttpHeaderValues.AUTHORIZATION_PREFIX_BEARER;
public AccessRefreshToken render(Integer expiresIn, String accessToken, String refreshToken) {
return new AccessRefreshToken(accessToken, refreshToken, BEARER_TOKEN_TYPE, expiresIn);
}
public AccessRefreshToken render(UserDetails userDetails, Integer expiresIn, String accessToken, String refreshToken) {
CustomAccessRefreshToken token = new CustomAccessRefreshToken(userDetails.getUsername(), userDetails.getRoles(), expiresIn, accessToken, refreshToken, BEARER_TOKEN_TYPE);
token.setUserId("Some user id, Some user id");
return token;
}
}
I've developed an application with React as front end and Spring Boot as back end. When I'm trying to send my JSON data to Spring Boot, I got the following exception:
2018-08-07 22:43:38.721 WARN 41036 --- [io-8080-exec-10]
.m.m.a.ExceptionHandlerExceptionResolver :
Resolved exception caused by Handler execution:
org.springframework.http.converter.HttpMessageNotReadableException:
JSON parse error:
Could not resolve type id 'connectionTechnologyName' as a subtype of [simple type, class com.model.ConnectionTechnologyDetails]:
no such class found; nested exception is com.fasterxml.jackson.databind.exc.InvalidTypeIdException:
Could not resolve type id 'connectionTechnologyName' as a subtype of [simple type, class com.model.ConnectionTechnologyDetails]:
no such class found at [Source: (PushbackInputStream); line: 1, column: 515] (through reference chain: com.payload.ConnectionRequest["connectionTechnologyDetails"])
Spring is searching for the type id for the assumed subtype ConnectionTechnologyName of my abstract class ConnectionTechnologyDetails. So Spring is searching for a sort of inheritance.
The abstact class ConnectionTechnologyDetails has a ConnectionTechnologyName as private attribute. The definition of ConnectionTechnologyName is furthermore an inner enum of the entitiy ConnectionTechnologyDetails. So there is no inheritance. I don't know what's the problem.
The JSON request from my React app is handled by the
following REST endpoint:
/**
* Creates a new connection.
*/
#PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Void> createConnection(#RequestBody ConnectionRequest connectionRequest) {
logger.warn("ConnectionController: createConnection()");
try {
ContactDetails requestContactDetails = connectionRequest.getContactDetails();
ContactDetails persistedContactDetails = null;
if (connectionRequest.getContactDetails() != null) {
ContactDetails tmpContactDetails = new ContactDetails(
userRepository.findById(connectionRequest.getUser().getId()).get(),
requestContactDetails.getCompany(),
requestContactDetails.getAddress(),
requestContactDetails.getCity(),
requestContactDetails.getState(),
requestContactDetails.getCountry(),
requestContactDetails.getPostalCode(),
requestContactDetails.getCompanyContactPersons(),
requestContactDetails.getHlagContactPersons());
logger.warn("ConnectionController: createConnection() contactDetails created");
persistedContactDetails = contactDetailsRepository.save(tmpContactDetails);
logger.warn("ConnectionController: createConnection() conetactDetails saved");
}
// business case
if (connectionRequest.getBusinessCaseName() != null) {
String requestBusinessCaseName = connectionRequest.getBusinessCaseName();
logger.warn("ConnectionController: createConnection() businessCaseName got");
}
// connection technology details
if (connectionRequest.getConnectionTechnologyDetails().getConnectionTechnologyName().name() == "SMTP") {
SMTP requestConnectionDetails = (SMTP) connectionRequest.getConnectionTechnologyDetails();
SMTP smtp = new SMTP(ConnectionTechnologyDetails.ConnectionTechnologyName.SMTP,
requestConnectionDetails.getSenderReceiverIds(),
requestConnectionDetails.getSmtp());
logger.warn("ConnectionController: createConnection() SMTP created");
SMTP persistedSmtp = connectionTechnologyDetailsRepository.save(smtp);
logger.warn("ConnectionController: createConnection() SMTP saved");
Connection newConnection =
connectionRepository.save(new Connection(
userRepository.findById(connectionRequest.getUser().getId()).get(),
contactDetailsRepository.findById(persistedContactDetails.getId()).get(),
businessCaseRepository
.findByBusinessCaseName(connectionRequest.getBusinessCaseName()).get()
, connectionRequest.getEdiMessageStandard(),
connectionTechnologyDetailsRepository.findById(persistedSmtp.getId()).get()));
return ResponseEntity.created(new URI("api/connections/" + newConnection.getId())).build();
} else {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
} catch (URISyntaxException e) {
return ResponseEntity.status(HttpStatus.CONFLICT).build();
}
}
The #ResponseBody connectionRequest is defined as:
package com.payload;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.model.*;
import com.model.AuditInformation.AuditInformation;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* Class that represents a connection creation requests message.
*/
public class ConnectionRequest {
/**
* Foreign key of the user who created this connection. The user is independent from the connectioin.
*/
#NotNull
private ConnectionRequestUser user;
/**
* Foreign key for the contact details. They are independent from the connection.
*/
#NotNull
private ContactDetails contactDetails;
/**
* Business case of connection.
*/
#NotNull
private String businessCaseName;
/**
* Holds if ANSI or EDIFACT message type.
*/
#NotNull
private Connection.EdiMessageStandard ediMessageStandard;
/**
* Connection technique used for this connection
*/
#NotNull
private ConnectionTechnologyDetails connectionTechnologyDetails;
public ConnectionRequest(ContactDetails contactDetails, #NotBlank String businessCaseName,
Connection.#NotNull EdiMessageStandard ediMessageStandard,
ConnectionTechnologyDetails connectionTechnologyDetails,ConnectionRequestUser user) {
this.user = user;
this.contactDetails = contactDetails;
this.businessCaseName = businessCaseName;
this.ediMessageStandard = ediMessageStandard;
this.connectionTechnologyDetails = connectionTechnologyDetails;
}
public ConnectionRequest() {
}
public ConnectionRequestUser getUser() {
return user;
}
public void setUser(ConnectionRequestUser user) {
this.user = user;
}
public ContactDetails getContactDetails() {
return contactDetails;
}
public void setContactDetails(ContactDetails contactDetails) {
this.contactDetails = contactDetails;
}
public String getBusinessCaseName() {
return businessCaseName;
}
public void setBusinessCaseName(String businessCaseName) {
this.businessCaseName = businessCaseName;
}
public Connection.EdiMessageStandard getEdiMessageStandard() {
return ediMessageStandard;
}
public void setEdiMessageStandard(Connection.EdiMessageStandard ediMessageStandard) {
this.ediMessageStandard = ediMessageStandard;
}
public ConnectionTechnologyDetails getConnectionTechnologyDetails() {
return connectionTechnologyDetails;
}
public void setConnectionTechnologyDetails(ConnectionTechnologyDetails connectionTechnologyDetails) {
this.connectionTechnologyDetails = connectionTechnologyDetails;
}
}
ConnectionTehcnologyDetails
import com.fasterxml.jackson.annotation.*;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
/**
* Represents common connection technology details.
*/
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(discriminatorType = DiscriminatorType.STRING, name = "TECH_TYPE")
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.WRAPPER_OBJECT, property = "#class")
#JsonSubTypes({
#JsonSubTypes.Type(value = AS2.class, name = "as2"),
#JsonSubTypes.Type(value = SMTP.class, name = "smtp"),
#JsonSubTypes.Type(value = FTP.class, name = "ftp"),
#JsonSubTypes.Type(value = FTPUnsec.class, name = "unsecFtp")
})
public abstract class ConnectionTechnologyDetails {
/**
* Unique id.
*/
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* To which connection the details belong.
*/
#OneToOne
#PrimaryKeyJoinColumn
#JsonIgnore
private Connection connection;
/**
* Name of the used technology.
*/
#NotNull
private ConnectionTechnologyName connectionTechnologyName;
/**
* Sender and receiver ids.
*/
#Embedded
private SenderReceiverIds senderReceiverIds;
public enum ConnectionTechnologyName {
SMTP,
AS2,
UNSECURE_FTP,
SECURE_FTP,
FTP_SECURE
}
/**
* Constructor
*
* #param connectionTechnologyName name of technology
* #param senderReceiverIds sender and receiver ids
*/
public ConnectionTechnologyDetails(ConnectionTechnologyName connectionTechnologyName, SenderReceiverIds senderReceiverIds) {
this.connectionTechnologyName = connectionTechnologyName;
this.senderReceiverIds = senderReceiverIds;
}
/**
* COnstructor
*/
public ConnectionTechnologyDetails() {
}
public ConnectionTechnologyName getConnectionTechnologyName() {
return connectionTechnologyName;
}
public void setConnectionTechnologyName(ConnectionTechnologyName connectionTechnologyName) {
this.connectionTechnologyName = connectionTechnologyName;
}
public SenderReceiverIds getSenderReceiverIds() {
return senderReceiverIds;
}
public void setSenderReceiverIds(SenderReceiverIds senderReceiverIds) {
this.senderReceiverIds = senderReceiverIds;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Override
public String toString() {
return "SenderAndReceiverIds: Name=" + getConnectionTechnologyName().name()
+ " SenderReceiverIds=" + getSenderReceiverIds();
}
}
Do I have to set an #Embedded annotation on ConnectionTechnologyName attribute in ConnectionTechnologyDetails? Or do i have to set an annotation to the public inner enum itself?
In my case... I was getting this error because I was mixing codehaus with fasterxml jackson libraries after a code merge.
Once I switched them to one or the other, it worked fine.
Using Spring Boot 1.3.1, I am having problems with #AuthenticationPrincipal.
This is my controller:
#RestController
#RequestMapping("/api/user")
public class UserController {
#RequestMapping("/")
public UserDto user(#AuthenticationPrincipal(errorOnInvalidType = true) User user) {
return UserDto.fromUser(user);
}
}
This is my custom User class:
#Entity()
#Table(name = "app_user")
public class User extends AbstractEntity<UserId> implements Serializable {
// ------------------------------ FIELDS ------------------------------
#NotNull
#Column(unique = true)
#Pattern(regexp = "[a-zA-Z_\\-\\.0-9]+")
#Size(min = 1, max = 20)
private String username;
private String password;
#Column(unique = true)
#Email
private String emailAddress;
#Enumerated(EnumType.STRING)
#NotNull
private UserRole role;
}
I also created a class to confirm to the UserDetails interface of Spring Security:
public class CustomUserDetails extends User implements UserDetails {
public CustomUserDetails(User user) {
super(user);
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Sets.newHashSet(new SimpleGrantedAuthority("ROLE_" + getRole().name()));
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
Then in my UserDetailsService:
#Override
public CustomUserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
com.company.app.domain.account.User user = userRepository.findByUsername(username);
if (user != null) {
return new CustomUserDetails(user);
} else {
throw new UsernameNotFoundException(format("User %s does not exist!", username));
}
}
I have an integration test that works perfectly. However, running the application itself (from IntelliJ IDEA), does not work. I get an exception:
"CustomUserDetails{id=UserId{id=401868de-99ff-4bae-bcb6-225e3062ed33}} is not assignable to class com.company.app.domain.account.User"
But this is not true, since CustomUserDetails is a subclass of my custom User class.
Checking with the debugger, I see that this code in AuthenticationPrincipalArgumentResolver fails:
if (principal != null
&& !parameter.getParameterType().isAssignableFrom(principal.getClass())) {
The classloader of parameter.getParameterType() is an instance of RestartClassloader, while principal.getClass() has a classloader that is an instance of Launcher$AppClassLoader.
Is this a bug in Spring Boot or Spring Security or am I doing something wrong?
UPDATE:
I can confirm that disabling devtools of Spring Boot makes it work:
public static void main(String[] args) {
System.setProperty("spring.devtools.restart.enabled", "false");
SpringApplication.run(MyApplication.class, args);
}
To help anyone else who hits the same problem, this is a limitation in Spring Boot DevTools and Spring Security OAuth. It's being tracked in this issue: https://github.com/spring-projects/spring-boot/issues/5071
I am using Spring Security in my application. I need loggedIn user details in the controllers of my application.
For that I am using this code
User loggedInUser = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
But on running this code I get a classcastexception
java.lang.ClassCastException: org.springframework.security.core.userdetails.User cannot be cast to model.User
To fix this I referred to this article
Initially I used a CustomUserServiceDetails class
#Service("myUserDetailService")
#Transactional
public class CustomUserDetailsService implements UserDetailsService {
private static final Logger logger = Logger.getLogger(CustomUserDetailsService.class);
#Autowired
private UserDAO userDAO;
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException, DataAccessException {
// returns the get(0) of the user list obtained from the db
User domainUser = userDAO.getUser(name);
logger.debug("User fetched from database in loadUserByUsername method " + domainUser);
Set<Role> roles = domainUser.getRole();
logger.debug("role of the user" + roles);
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
for(Role role: roles){
authorities.add(new SimpleGrantedAuthority(role.getRole()));
logger.debug("role" + role + " role.getRole()" + (role.getRole()));
}
boolean credentialNonExpired = true;
return new org.springframework.security.core.userdetails.User(domainUser.getProfileName(), domainUser.getPassword(), domainUser.isAccountEnabled(),
domainUser.isAccountNonExpired(), credentialNonExpired, domainUser.isAccountNonLocked(),authorities);
}
}
But after referring to the article I removed the setting of GrantedAuthorities from here and moved it to my User class. Implemented spring-security UserDetails class in my User class
Now I have an extra property in my User class
#Entity
#Table(name = "user")
public class User implements UserDetails {
private Collection<GrantedAuthority> authorities;
with a setMethod
public void setAuthorities(Set<Role> roles) {
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
for(Role role: roles){
authorities.add(new SimpleGrantedAuthority(role.getRole()));}
}
A. I am not sure how to map this property to the database. The existing User table schema doesn't contain a GrantedAuthority column besides It's not even a primitive type. I am using Hibernate for object mapping. Can anyone advice me the correct approach to obtain the user class info in the controllers?
B. I also considered the approach of extending the spring's User class and overloading the constructor of my User class. But then every time I initialize my User anywhere in the code I have to provide all the constructors parameters which is not good at all.
Instead of using
User loggedInUser = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try this
Authentication loggedInUser = SecurityContextHolder.getContext().getAuthentication();
String username = loggedInUser.getName();
References:
https://www.mkyong.com/spring-security/get-current-logged-in-username-in-spring-security/
Fixed the issue
Solution
Created a CustomUserDetail class which implements Spring's UserDetails interface. Injected my model User class in it.
public class CustomUserDetail implements UserDetails{
private static final long serialVersionUID = 1L;
private User user;
Set<GrantedAuthority> authorities=null;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
public void setAuthorities(Set<GrantedAuthority> authorities)
{
this.authorities=authorities;
}
public String getPassword() {
return user.getPassword();
}
public String getUsername() {
return user.getProfileName();
}
public boolean isAccountNonExpired() {
return user.isAccountNonExpired();
}
public boolean isAccountNonLocked() {
return user.isAccountNonLocked();
}
public boolean isCredentialsNonExpired() {
return user.isCredentialsNonExpired();
}
public boolean isEnabled() {
return user.isAccountEnabled();
}
}
CustomUserServiceDetails
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserDAO userDAO;
public CustomUserDetail loadUserByUsername(String name) throws UsernameNotFoundException, DataAccessException {
// returns the get(0) of the user list obtained from the db
User domainUser = userDAO.getUser(name);
Set<Role> roles = domainUser.getRole();
logger.debug("role of the user" + roles);
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
for(Role role: roles){
authorities.add(new SimpleGrantedAuthority(role.getRole()));
logger.debug("role" + role + " role.getRole()" + (role.getRole()));
}
CustomUserDetail customUserDetail=new CustomUserDetail();
customUserDetail.setUser(domainUser);
customUserDetail.setAuthorities(authorities);
return customUserDetail;
}
}
In my controller method
CustomUserDetail myUserDetails = (CustomUserDetail) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Integer userId=myUserDetails.getUser().getUserId(); //Fetch the custom property in User class
The method .getPrincipal() returns the object created and returned it in the method loadUserByUsername.
If you want an User you must return in the method loadUserByUsername an User, not an org.springframework.security.core.userdetails.User
I'm using Spring security for the login. I have the User.java which contains user-details.
#Entity(name = "user_table")
//#Table(name = "user_table")
public class User {
#Id
#Column(name = "id")
private String userId;
#Column(name = "email" ,unique = true)
private String userEmail;
#Column(name = "password")
private String userPassword;
//getter and setters
}
I'm getting the whole data of the current user from the table by using spring security. This is the code:
public User findUserByEmail(String email) {
List<User> users = new ArrayList<User>();
try{
users = sessionFactory.getCurrentSession().createQuery("from user_table where email= ?").setParameter(0, email).list();
System.out.println("user is " +users);
}catch(Exception e){
System.out.println(e.getMessage());
e.printStackTrace();
}
if (users.size() > 0) {
return users.get(0);
} else {
return null;
}
}
#Override
public User getCurrentUser() {
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
User currentUser = new User();
if (!(auth instanceof AnonymousAuthenticationToken)) {
UserDetails userDetails = (UserDetails) auth.getPrincipal();
System.out.println("User has authorities: "
+ userDetails.getAuthorities());
System.out.println("USERNAME:: "+userDetails.getUsername());
currentUser = findUserByEmail(userDetails
.getUsername());
System.out.println("currentUser "+currentUser);
System.out.println("currentUser "+currentUser.getUserId());
return currentUser;
}
return null;
}
What I want is to send the user id which I'm getting from currentUser.getUserId() to some other method. In that method I'm mapping to some other table like user_detail table where id is primary key. By sending id, I will get the other user_details which are not present in the user_table.
This is my UserDetail:
#Entity(name = "user_detail")
#Table(name = "user_detail")
public class UserDetail {
#Id
#GeneratedValue
#Column(name = "id")
private String userId;
//some other details like Address .
//getter and setter.
}
From controller I'm calling the above method like this:
UserService userService = new UserService();
User user=userDao.getCurrentUser();
String userId = user.getUserId();
System.out.println(userId);
UserDetail u=userDao.findUserById(userId);
and this is the method where I pass the current user id :
public UserDetail findUserById(String id) {
// TODO Auto-generated method stub
List<String> users = new ArrayList<String>();
try{
users = sessionFactory.getCurrentSession().createQuery("from user_detail where id= ?").setParameter(0, id).list();
System.out.println("user is " +users);
}catch(Exception e){
System.out.println(e.getMessage());
e.printStackTrace();
}
if (users.size() > 0) {
return null;
} else {
return null;
}
}
Now the result I'm getting here is null . Like user is null. What I'm doing wrong here?
There are several problems in your code. Just to point out some of them:
UserService userService = new UserService(); - you're manually creating the service object and not letting Spring-MVC injecting it into your controller, i.e. :
#Autowired
private UserService userService ;
UserDAO should be injected in your service, and not called from your controller :
class UserServiceImpl implements UserService{
#Autowired
private UserDAO userDAO;
}
All operations from your controller should call services methods and not DAO's methods. The service should use the DAO for database access. i.e.
UserDetail u=userDao.findUserById(userId);
should become
UserDetail u = userService.findUserById(userId);
and in your service :
class UserServiceImpl implements UserService{
#Autowire
private UserDAO userDAO;
#Override
public UserDetail findUserById(Long userId){
return userDAO.findUserById(userId);
}
}
if (users.size() > 0) {
return null;
} else {
return null;
}
is always returning null. Should be :
if (`users.isEmpty()){
return users.get(0);
}else { return null;}
users = sessionFactory.getCurrentSession().createQuery("from user_detail where id= ?").setParameter(0, id).list();
Your query is wrong. You should use your current bean class name and not the table name in your query, i.e. createQuery("FROM UserDetail WHERE id = ?")