Reputation: 375
I'm using thymeleaf-extras-springsecurity4 with spring security on my project. The problem is I cannot get user's extra fields (which means user information on database except username
, password
, enabled
, etc. given by UserDetails
) by using <span sec:authentication="principal.something" />
.
Heres are my simple codes:
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@EqualsAndHashCode
@Entity
@Table(name = "users", schema = "myschema")
public class UserEntity implements UserDetails {
@Id
@GeneratedValue
@Column(name = "id", nullable = false)
private int id;
@Basic
@Column(name = "username", nullable = false, unique = true, length = 64)
private String username;
@Basic
@Column(name = "password", nullable = false, columnDefinition = "TEXT")
private String password;
@Basic
@Column(name = "enabled", nullable = false, columnDefinition = "BIT")
private boolean enabled;
@Basic
@Column(name = "phone", nullable = false, length = 16)
private String phone;
@OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
private List<AuthorityEntity> authorities;
@Override
public boolean isAccountNonExpired() {
return enabled;
}
@Override
public boolean isAccountNonLocked() {
return enabled;
}
@Override
public boolean isCredentialsNonExpired() {
return enabled;
}
@Override
public String toString() {
return username;
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "authorities", schema = "myschema",
uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "authority"}))
public class AuthorityEntity implements GrantedAuthority {
@Id
@GeneratedValue
@Column(name = "id", nullable = false)
private int id;
@Basic
@Column(name = "authority", nullable = false, length = 24)
private String authority;
@ManyToOne
@JoinColumn(name = "user_id", referencedColumnName = "id", nullable = false)
private UserEntity user;
}
@Repository
public interface UserRepository extends JpaRepository<UserEntity, Integer> {
UserEntity findOneByUsernameAndEnabledTrue(String username);
}
@Service
public class UserService {
private UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public UserEntity loadUserByUsername(String username) {
return userRepository.findOneByUsernameAndEnabledTrue(username);
}
}
@Service
public class SecurityService implements UserDetailsService {
private UserService userService;
@Autowired
public SecurityService(UserService userService) {
this.userService = userService;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetails user = userService.loadUserByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(username);
}
return user;
}
}
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private SecurityService securityService;
@Autowired
public SecurityConfig(SecurityService securityService) {
this.securityService = securityService;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/user/login").anonymous()
.antMatchers("/**").hasAnyRole("ADMIN", "USER")
.and()
.formLogin()
.loginPage("/user/login")
.defaultSuccessUrl("/")
.and()
.logout()
.logoutUrl("/user/logout")
.logoutSuccessUrl("/")
.and()
.exceptionHandling()
.accessDeniedPage("/error/403");
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
auth.userDetailsService(securityService).passwordEncoder(passwordEncoder);
}
}
<!DOCTYPE html>
<html lang="ko"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"
layout:decorator="layout/base">
<th:block layout:fragment="content">
<h1>Main Page</h1>
<p sec:authentication="principal.username">Username</p>
<p sec:authentication="principal.phone">Phone</p>
</th:block>
In index.html
, sec:authentication="principal.username"
works as expected, but sec:authentication="principal.phone"
does not despite my UserDetailsService
implementation stores UserEntry
which implements UserDetails
with extra field phone
.
sec:authentication="principal.phone"
work well? (or "princiapl.getPhone()"
respectively)UserEntry
object without plugging model explicitly for instance through mav
of each controller method? Does AOP deal with this?(Additional) In many other examples applying spring security, they don't implement UserDetails
on UserEntry
(or similar classes), but make a new UserDetails
instance in their UserDetailService
implementation like
@Override
public UserDetails loadUserByUsername(String userName)
throws UsernameNotFoundException {
UserInfo activeUserInfo = userInfoDAO.getActiveUser(userName);
GrantedAuthority authority = new SimpleGrantedAuthority(activeUserInfo.getRole());
UserDetails userDetails = (UserDetails)new User(activeUserInfo.getUserName(),
activeUserInfo.getPassword(), Arrays.asList(authority));
return userDetails;
}
(from here). I think my structure is not a good design but I don't know exactly why. Is there any comment for my class design?
If my questions are too vague, let me know so then I would update this more concrete.
Upvotes: 3
Views: 3692
Reputation: 5097
In order to use additional fields contained in your user's data in Thymeleaf
, you must go through the next steps.
loadUserByUsername
, so that it returns your custom user.${#authentication.getPrincipal()}
, instead of sec
.STEP 1
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;
// Our own implementation of the Spring Security User.
public class MyUser extends User {
// Here we add the extra fields of our users.
private String phone;
private static final long serialVersionUID = 1L;
public MyUser(String username,
String password,
Collection<GrantedAuthority> authorities,
String phone) {
super(username, password, authorities);
this.phone = phone;
}
public String getPhone() {
return realName;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
STEP 2
@Override
public MyUser loadUserByUsername(String userName)
throws AuthenticationException {
// Fetch the user.
UserDetails user = userService.loadUserByUsername(username);
// For each user's authority, add it into our authorities' collection.
Collection<GrantedAuthority> grantedAuthorities = new LinkedList<GrantedAuthority>();
if (user.getAuthorities().size() > 0){
for (Authority authority : user.getAuthorities()) {
// Add a new GrantedAuthority for each user's authorities.
grantedAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority()));
}
}
return new MyUser(user.getUsername(), user.getPassword(), grantedAuthorities, user.getPhone());
}
STEP 3
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
STEP 4
<th:block th:with="auth=${#authentication.getPrincipal()}">
<p th:text="${auth ? auth.phone : 'NULL'}">Phone</p>
</th:block>
Upvotes: 2