I am not so into Spring Security. I am working on a Spring Boot project (implementing some REST web sercices) where someone else have implemented Spring Security to perform authentication. It seems works fine but I have some doubts about how it exactly works (the architecture).
So basically I have the following classes:
1) User that is my model class representing a user:
#Entity
#Table(name = "user",
uniqueConstraints = {
#UniqueConstraint(columnNames = {"email","username"})
})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
//#Pattern(regexp="\\b[A-Z0-9._%+-]+#[A-Z0-9.-]+\\.[A-Z]{2,4}\\b",flags = Pattern.Flag.CASE_INSENSITIVE)
#Column(name="email", unique=true,nullable=false)
private String email;
#NotNull
#Column(name="registration_date",nullable=false)
private Date registration_date;
#NotBlank
#Column(name="username",nullable = false,unique=true)
private String username;
#NotBlank
#Column(name="password",nullable = false)
private String password;
#Column(name="enabled", nullable = false)
private boolean enabled;
#ManyToMany
#JoinTable(name = "user_user_roles", joinColumns = {
#JoinColumn(name = "id_user", updatable = true) },
inverseJoinColumns = { #JoinColumn(name = "id_roles",
updatable = true)},
uniqueConstraints={#UniqueConstraint(columnNames = {"id_user","id_roles"})}
)
#Cascade({CascadeType.DETACH,CascadeType.MERGE,CascadeType.REFRESH,CascadeType.PERSIST,
CascadeType.SAVE_UPDATE})
private List<UserRole> userRoles;
// CONSTRUCTOR, GETTER AND SETTER METHODS
}
It is an hibernate class that provide the join with the user_user_roles database table containing the list of user rool associated to the specific user (I think the ROLE_ADMIN, ROLE_USER, etcetc)
2) Then I have CustomUserDetails class that extends User and implements Spring Security UserDetails interface.
So it means that will contain all the information related to a specific user (among which the role associated to this user) and implement all the methods declared in the UserDetails interface.
public class CustomUserDetails extends User implements UserDetails {
private static final long serialVersionUID = 1L;
public CustomUserDetails(User user){
super(user);
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
for(UserRole role : this.getUserRoles() ){
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(role.getName());
authorities.add(grantedAuthority);
}
return authorities;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
#Override
public String getUsername() {
return super.getUsername();
}
}
It seems to me that this class represent the bridge between Spring application and Spring Security (something like that carry the roles information related to a specific user, is it?).
In this class the information related to the list of roles of an user is contained into a collection of generic object that extends GrantedAuthority.
I think that the GrantedAuthority objects represents the roles of a user (infact are builded by role.getName() that is the string representing a role of the current user). Is it this reasoning correct?
Basically the prvious implementation only return the collection of GrantedAuthority related to a specific user, is it?
3) The CustomUserDetailsService class implementing Spring Security UserDetailsService interface:
#Transactional
#Service("customUserDetailsService")
public class CustomUserDetailsService implements UserDetailsService{
#Autowired
private UserDAO userDao;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userDao.findByUsername(username);
if(user == null){
throw new UsernameNotFoundException("No user present with username: "+username);
}else{
return new CustomUserDetails(user);
}
}
}
Basically this is a service that only implements the loadUserByUsername(String username)) method that do:
First retrieve the information related the user (a User object) including the list of his roles (List userRoles).
Then use this User object to build the previous CustomUserDetails used to retrieve the collection of the GrantedAuthority related to the user.
Is all these reasoning correct?
Then I also have this WebSecurityConfig that I think represent the Spring Security configuration:
#Configuration
#EnableWebSecurity
#ComponentScan(basePackageClasses = CustomUserDetailsService.class)
#EnableAutoConfiguration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
}
}
This is pretty obsucre for me. What exactly does?
From what I have understand it is enabling the REST web services security by ** #EnableWebSecurity**.
But why does the autowire of the UserDetailsService bean?
And what exactly foes this empty configure() method?
Your reasoning is correct.
Spring-security requires you to create a service which implements UserDetailsService. It expects service to have loadUserByUsername method which returns user object (which needs to implement Spring's User class). This instance of user is used to get authorities so that you can restrict access to certain urls. i.e. you can map url access to users with specific authorities.
In terms of the empty method configure(), it is used to for authentication and authorisation of the urls (mentioned above) and few more things. In fact, in terms of security, this is most flexible & powerful method available.
My project's config method looks like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/","/static/**").permitAll()
.mvcMatchers("/admin").access("hasAuthority('ROLE_ADMIN')")
.mvcMatchers("/employees").access("hasAuthority('ROLE_STAFF')")
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.logout().logoutSuccessUrl("/")
.and()
.headers().contentSecurityPolicy("default-src 'self' " +
"https://ajax.googleapis.com " +
"https://cdnjs.cloudfare.com " +
"style-src 'self' 'unsafe-inline' ");
}
The above example
Ensures all requests are authenticated and authorized.
Permits free access to static resources
Ensures url /admin is accessible only by users with role ADMIN
Setup basic login prompt for authentication
Sets logout url
Sets up cookie based CSRF token system
Sets content security policy
To understand more about all spring-security features, I highly recommend these resources.
excellent video presentations from Rob Winch (lead on Spring Security project)
Site with good examples
Sample code repo
Related
I've multiple users which I want to configure into spring-boot's UserDetailsService for basic authentication.
User has a additional field id tagged with it.
import org.springframework.security.core.userdetails.UserDetails;
public class User implements UserDetails {
private final String username;
private final String password;
private final String id;
private static final String ROLE_USER = "ROLE_USER";
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(ROLE_USER);
return Stream.of(simpleGrantedAuthority).collect(Collectors.toList());
}
// Getter & setter
}
Properties yml looks like:
basic-auth:
oliver:
password: twist
id: 1
ruskin:
password: bond
id: 2
mark:
password: twain
id: 3
In UserDetailsService, I'm not sure how to configure users using application properties dynamically.
public class UserService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) {
String encodedPassword = passwordEncoder.encode( // How to fetch password );
String id = // How to fetch id
return new User(username, encodedPassword, id);
}
}
You can do this in different ways.
For example, let's assume your Basic Authentication properties YAML file, with minimum tweaks for simplicity:
basic-auth:
users:
oliver:
password: twist
id: 1
ruskin:
password: bond
id: 2
mark:
password: twain
id: 3
To make this information accessible to your UserDetailsService service you can use encapsulate this information in form of #ConfigurationProperties, something like the following:
#Component
#ConfigurationProperties(prefix = "basic-auth")
public class BasicAuthProperties {
private Map<String, Credential> users = new HashMap<>();
// Getter & setter
// Convenient method
public Optional<Credential> getCredential(String username) {
Optional.ofNullable(users.get(username));
}
public static class Credential {
private int id;
private String password;
// Getter & setter
}
}
And use this #ConfigurationProperties in your UserDetailsService:
public class UserService implements UserDetailsService {
private final BasicAuthProperties basicAuthProperties;
public UserService(BasicAuthProperties basicAuthProperties) {
this.basicAuthProperties = basicAuthProperties;
}
#Override
public UserDetails loadUserByUsername(String username) {
Optional<BasicAuthProperties.Credential> optCredential = basicAuthProperties.getCredential(username);
if (!optCredential.isPresent()) {
// handle as appropriate, consider at least logging it
return null;
}
BasicAuthProperties.Credential credential = optCredential.get();
String encodedPassword = passwordEncoder.encode(credential.getPassword());
// I assume int, the value is numeric in your YAML file, but modify it
// if necessary
int id = credential.getId();
return new User(username, encodedPassword, id);
}
}
This is my solution for Basic Authentication with yaml from scratch.
1.You need to put this file to the resource folder
appUserList.yml:
constants:
appUserList:
- userId: 1
userName: oliver
password: twist
role: USER
- userId: 2
userName: ruskin
password: bond
role: USER
- userId: 3
userName: mark
password: twain
role: ADMIN
2.You need to make an AppUser class:
public class AppUser {
private String userId;
private String userName;
private String password;
private String role;
public AppUser() {
}
public AppUser(String userId, String userName, String password, String role) {
this.userId = userId;
this.userName = userName;
this.password = password;
this.role = role;
}
//Getter and Setters
}
3.You need to make a YamlPropertySourceFactory class:
public class YamlPropertySourceFactory implements PropertySourceFactory {
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource) throws IOException {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(encodedResource.getResource());
Properties properties = factory.getObject();
return new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties);
}
}
4.Now you can use the yaml file and the other two classes to create the AppUserProperties class:
#Component
#PropertySource(value = "classpath:appUserList.yml", factory = YamlPropertySourceFactory.class)
#ConfigurationProperties(prefix = "constants")
public class AppUserProperties {
private List<AppUser> appUserList;
public List<AppUser> getAppUserList() {
return appUserList;
}
public void setAppUserList(List<AppUser> appUserList) {
this.appUserList = appUserList;
}
}
5.Now you need to make a SecurityConf class where you will get the users with this method: appUserProperties.getAppUserList(). If you want use other identifiers for the users, just create the parameters in the AppUser class.
#EnableGlobalMethodSecurity(securedEnabled = true)
#Configuration
public class SecurityConf extends WebSecurityConfigurerAdapter {
#Autowired
AppUserProperties appUserProperties;
#Autowired
public void configureAuth(AuthenticationManagerBuilder auth) throws Exception {
List<AppUser> appUserList = appUserProperties.getAppUserList();
for (AppUser appUser : appUserList) {
auth
.inMemoryAuthentication()
.withUser(appUser.getUserName())
.password(appUser.getPassword())
.roles(appUser.getRole());
}
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/login?logout")
.permitAll();
}
}
These are all the important steps for it to work.
Otherwise, here is the fully working code:
https://github.com/FerencHrutka/Basic-Auth-with-yaml-file
I hope this answers your question.
hi what i trying to achieve is to protect a url that only one role can access to it, when i try add .hasRole("USER"), still the other role can access it. Here is how i do it :
here is my controller :
#RestController
#RequestMapping("/couponapi")
public class CouponController {
#Autowired
CouponRepository couponRepository;
#PostMapping("/coupons")
public Coupon save(#RequestBody Coupon coupon) {
return couponRepository.save(coupon);
}
#GetMapping("/coupons/{code}")
public Coupon findByCode(#PathVariable("code") String code) {
return couponRepository.findByCode(code);
}
#GetMapping("/something")
public Coupon findByCodeX() {
return couponRepository.findByCode("SUPERSALE");
}
}
i want to protect #GetMapping("/something") only for ROLE_ADMIN, here is how my Spring Security Configuration looked like :
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailServiceImpl userDetailService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic();
http.authorizeRequests()
.antMatchers(HttpMethod.GET,"/couponapi/coupons/**").hasRole("USER")
.antMatchers(HttpMethod.POST,"/couponapi/coupons/**").hasRole("USER")
.antMatchers("/couponapi/something").hasRole("ADMIN")
.antMatchers("/**").authenticated()
.and().httpBasic().and().csrf().disable();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
here is my role class :
#Data
#EqualsAndHashCode(of = "id")
#ToString(of = { "id" })
#Entity
public class Roles implements GrantedAuthority {
private static final long serialVersionUID = -7314956574144971210L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany(mappedBy = "roles")
private Set<Users> users;
#Override
public String getAuthority() {
return null;
}
}
and here is my service that implements UserDetailsService class :
#Service
public class UserDetailServiceImpl implements UserDetailsService {
#Autowired
UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
Users users = userRepository.findByEmail(s);
if(users == null) {
throw new UsernameNotFoundException("Username Not Found");
}
return new User(users.getEmail(), users.getPassword(), users.getRoles());
}
}
and here is my database role data :
as you can see i have ROLE_USER and ROLE_ADMIN
and here is my joined database
** i just updated my question and i have answer of half of my issue, please read my answer below to see the latest issue
In spring security the most restrictive rules are defined first, therefore your configuration should look like this
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic();
http.authorizeRequests()
.antMatchers(HttpMethod.GET,"/**/something").hasRole("USER")
.antMatchers("/**").authenticated()
.and().httpBasic().and().csrf().disable();
}
i find the culprit here, but not entirely i still missing the functional of HttpMethod one. here is how i fixed the role, in my Role class i do a mistake that i trying to implements the GrantedAuthority class, that the thing that cause this trouble (without HttpMethod issue). and here is how i fixed it
first, delete the impements and turn the role into usual #Entity class :
#Data
#EqualsAndHashCode(of = "id")
#ToString(of = { "id" })
#Entity
public class Roles {
private static final long serialVersionUID = -7314956574144971210L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany(mappedBy = "roles")
private Set<Users> users;
}
then at class that implements UserDetailsService, add this code :
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
users.getRoles().forEach(d -> {
grantedAuthorities.add(new SimpleGrantedAuthority(d.getName()));
});
i cannot explain it in details, but i think List only need 1 string, that's string is a role name. then here is the full code :
#Service
public class UserDetailServiceImpl implements UserDetailsService {
#Autowired
UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
Users users = userRepository.findByEmail(s);
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
users.getRoles().forEach(d -> {
grantedAuthorities.add(new SimpleGrantedAuthority(d.getName()));
});
if(users == null) {
throw new UsernameNotFoundException("Username Not Found");
}
return new User(users.getEmail(), users.getPassword(), grantedAuthorities);
}
}
if anyone can find out the fix for this line .antMatchers(HttpMethod.GET,"/**/something").hasRole("USER") which is i want to use HttpMethod to differentiate each method with the same url, if someone have the answer, i will accept your answer. waiting for it
thanks for your good presentation of your problem.
That’s what I underestood, you want to give access only to user with Admin role to this URL /couponapi/something and give access to this URL /couponapi/coupons/** for all authenticated users whatever their roles (ADMIN or USER)
Try use hasAuthority instead of hasRole and delete the first line http.httpBasic() , it worked for me.
So in the WebSecurityConfig.java file:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/couponapi/something").hasAuthority("ROLE_ADMIN")
.antMatchers("/**").authenticated()
.and().httpBasic().and().csrf().disable();
}
Then In the above section of code, you only give access to users with ADMIN authority to access this url /couponapi/something so user with USER authority can’t access it.
And since you have declared a password encoder bean, you can use it with the AuthenticationManagerBuilder
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
I try to build an application based on Spring Boot (1.2.7.RELEASE) and Vaadin (7.6.3). My problem is that I'm not able to integrate Spring Security with Vaadin. I want a custom Vaadin built LoginScreen and Spring Security control. My project setup is as follows:
#Configuration
#EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().
exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")).accessDeniedPage("/accessDenied")
.and().authorizeRequests()
.antMatchers("/VAADIN/**", "/PUSH/**", "/UIDL/**", "/login", "/login/**", "/error/**", "/accessDenied/**", "/vaadinServlet/**").permitAll()
.antMatchers("/authorized", "/**").fullyAuthenticated();
}
}
And here is my Vaadin login UI
#SpringUI(path = "/login")
#Title("LoginPage")
#Theme("valo")
public class LoginUI extends UI {
TextField user;
PasswordField password;
Button loginButton = new Button("Login", this::loginButtonClick);
private static final String username = "username";
private static final String passwordValue = "test123";
#Override
protected void init(VaadinRequest request) {
setSizeFull();
user = new TextField("User:");
user.setWidth("300px");
user.setRequired(true);
user.setInputPrompt("Your username");
password = new PasswordField("Password:");
password.setWidth("300px");
password.setRequired(true);
password.setValue("");
password.setNullRepresentation("");
VerticalLayout fields = new VerticalLayout(user, password, loginButton);
fields.setCaption("Please login to access the application");
fields.setSpacing(true);
fields.setMargin(new MarginInfo(true, true, true, false));
fields.setSizeUndefined();
VerticalLayout uiLayout = new VerticalLayout(fields);
uiLayout.setSizeFull();
uiLayout.setComponentAlignment(fields, Alignment.MIDDLE_CENTER);
setStyleName(Reindeer.LAYOUT_BLUE);
setFocusedComponent(user);
setContent(uiLayout);
}
public void loginButtonClick(Button.ClickEvent e) {
//authorize/authenticate user
//tell spring that my user is authenticated and dispatch to my mainUI
}
}
When I start my application spring redirects me to my login UI, which is fine.
But I don't know how to authenticate the user against the spring security mechanism and dispatch to my mainUI.
I'm also facing the problem with csrf tokens, if I don't disable csrf I'll get the crfs token is null exception. I found a lot of examples handling those problems but there is no solution provided with Vaadin.
Thanks for help.
After a week of struggle and research, I was able to get this working. It was very exhausting because there a lot of information and solutions on the internet, most of them using xml based configuration or JSP form based login, until now I couldn't find another solution without a xml config file using the Vaadin framework to create a custom login page.
I cannot guarantee that this is best practice or the easiest solution. Moreover I didn't evaluate every part of it, the login mechanism works as far as I can see but maybe there could be some problems which I haven't discovered yet.
Maybe it'll help someone who face the same problem so I'll post my answer here.
First of all my securityConfig:
#Resource(name = "authService")
private UserDetailsService userDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().
exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")).accessDeniedPage("/accessDenied")
.and().authorizeRequests()
.antMatchers("/VAADIN/**", "/PUSH/**", "/UIDL/**", "/login", "/login/**", "/error/**", "/accessDenied/**", "/vaadinServlet/**").permitAll()
.antMatchers("/authorized", "/**").fullyAuthenticated();
}
#Bean
public DaoAuthenticationProvider createDaoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
You have to disable crsf but that's no problem since vaadin has its own crsf protection.
Furthermore you need to permit some URIs so vaadin can access its resources: /VAADIN/** is absolutely necessary, I would also reccommend to allow /vaadinServlet/**, /PUSH/** and /HEARTBEAT/**, but it depends on what parts of Vaadin you use.
Second my UserDetailsService:
#Service("authService")
public class AuthService implements UserDetailsService {
#Autowired
CustomUserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
CustomUser user = userRepository.findCustomUserByUserName(userName);
return user;
}
}
The DaoAuthenticationProvider uses the UserDetails' loadUserByUserName method to get an object of a class which implements the UserDetails interface. Be aware that every attribute described in the UserDetailsInterface must not be null, otherwise you get a NullPointerException thrown by the DaoAuthenticationProvider later.
I created a JPA Entity which implements the UserDetails interface:
#Entity
public class CustomUser implements UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
#ManyToMany(fetch = FetchType.EAGER)
Collection<Authorities> authorities;
String password;
String userName;
Boolean accountNonExpired;
Boolean accountNonLocked;
Boolean credentialsNonExpired;
Boolean enabled;
#Autowired
#Transient
BCryptPasswordEncoder passwordEncoder;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
#Override
public String getPassword() {
return password;
}
#Override
public String getUsername() {
return userName;
}
#Override
public boolean isAccountNonExpired() {
return accountNonExpired;
}
#Override
public boolean isAccountNonLocked() {
return accountNonLocked;
}
#Override
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
#Override
public boolean isEnabled() {
return enabled;
}
public void setId(Long id) {
this.id = id;
}
public void setAuthorities(Collection<Authorities> authorities) {
this.authorities = authorities;
}
public void setPassword(String password) {
this.password = passwordEncoder.encode(password);
}
public void setUserName(String userName) {
this.userName = userName;
}
public void setAccountNonExpired(Boolean accountNonExpired) {
this.accountNonExpired = accountNonExpired;
}
public void setAccountNonLocked(Boolean accountNonLocked) {
this.accountNonLocked = accountNonLocked;
}
public void setCredentialsNonExpired(Boolean credentialsNonExpired) {
this.credentialsNonExpired = credentialsNonExpired;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
}
Plus the Authorities Entity:
#Entity
public class Authorities implements GrantedAuthority {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String authority;
#Override
public String getAuthority() {
return authority;
}
public void setAuthority(String authority) {
this.authority = authority;
}
}
Obviously you'll have to store some user data in the database first before the authentication will work.
In Vaadin I couldn't get it worked by using one UI with different views, so I ended up using two UIs one for login and another for the main application.
In Vaadin I could set the URI path in the class annotation:
#SpringUI(path = "/login")
#Title("LoginPage")
#Theme("valo")
public class LoginUI extends UI {
//...
}
With this configuration my login screen is available at localhost:port/login
and my main application at localhost:port/main.
I login the user programmatically within a button.click method in my loginUI:
Authentication auth = new UsernamePasswordAuthenticationToken(userName.getValue(),password.getValue());
Authentication authenticated = daoAuthenticationProvider.authenticate(auth);
SecurityContextHolder.getContext().setAuthentication(authenticated);
//redirect to main application
getPage().setLocation("/main");
I hope it helped some of you.
As an alternative to the given approach, you can also rely on vaadin4spring, an 'unofficial' set of extensions to further integrate Vaadin and Spring.
It currently offers two ways to integrate Spring Security, which seem to work fine (even if they do not work for you, the code samples should give you enough insights to write custom integration code).
I would like to know how to correctly store and retrieve my user in the session via Spring Security. I am using an AuthenticationManager. So far I am using the following approach which I know isn't the cleanest
public class UserController {
#Autowired
private UserService userService;
#RequestMapping(value="/myAccount", method=RequestMethod.GET)
public void myAccount(HttpServletRequest request){
//Verify Logged in user
User user = null;
UserProfile loggedinUser = (UserProfile) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if(loggedinUser != null){
user = userService.loadUserByUsername(loggedinUser.getUsername());
}
//Store User in session
Session session = request.getSession(true);
session.setAttribute("user", user);
}
}
I took a look at the answer here: Set User Object in session using Spring
But I don't fully understand how I should implement it. Could I possibly implement my User class like this
public class User implements UserDetails {
private String username;
private String password;
private String email;
......
}
And then modify my controller like this
public class UserController {
#Autowired
private UserDetailsService userDetailsService;
#RequestMapping(value="/myAccount", method=RequestMethod.GET)
public void myAccount(){
User user = (User)SecurityContextHolder.
getContext().getAuthentication().getPrincipal();
}
}
From my understanding, Spring will automatically load my custom User from the database and store it in the session?
Thanks
I am trying to save additional data in de user principal object.
What i did was:
implement the "UserDetails" interface to my existing user class where my additional data is saved ( like email address etc. ).
#Entity
public class User implements UserDetails {
Then i created a UserDetailsService implementation:
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
UserDAO userDAO;
#Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
User user = userDAO.findOneByUsername(username);
if (user == null)
throw new UsernameNotFoundException("username " + username
+ " not found");
System.out.println("---------------------> FOUND ------------->"
+ user.getEmail());
return user;
}
}
Last step was to add the UserDetailsService in my Security configuration.
#Configuration
#EnableWebMvcSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(userDetailsService());
// ...
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.userDetailsService(userDetailsService());
// ...
}
#Override
protected UserDetailsService userDetailsService() {
return userDetailsService;
}
I see in my console that "loadUserByName" gets called twice ( because of the "Found" output ).
When i try to access the principal object in my controller ->
System.out.println(SecurityContextHolder.getContext()
.getAuthentication().getPrincipal());
I dont get my additional data.
When i try to cast it to my User object i get a could not cast exception.
Is there anything I am missing??
Thank you in advance.
Ok. My problem was hidden in the code i didnt post.
I thought this detailsService is only to get additional details but it is used for the login itself.
I had "jdbcAuthentication" configured additionally and spring seemed to use this always.
Now that i only got the detailsService configured everything works fine.
edit.:
so i only had to delete this code:
auth.jdbcAuthentication() .dataSource(dataSource)
* .passwordEncoder(passwordEncoder) .usersByUsernameQuery(
// ....
And now it also works with my code in the question above.
Create Extention class:
public class CustomUserDetails extends org.springframework.security.core.userdetails.User{
private User user;
public CustomUserDetails(User user, Collection<? extends GrantedAuthority> authorities) {
super(user.getName(), user.getPassword(), authorities);
this.user = user;
}
public CustomUserDetails(User user, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
super(user.getName(), user.getPassword(), enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
this.user = user;
}
public User getUser() {
return user;
}
}
Than add it to UserDetailsService:
#Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException, DataAccessException {
UserDetails userDetails = null;
User user = userService.getByLogin(login);
userDetails = new CustomUserDetails(user,
true, true, true, true,
getAuthorities(user.getRole()));
return userDetails;
}
Get it!
(CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()