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:
UserEntity (implements UserDetails)
@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;
}
}
AuthorityEntity (implements GrantedAuthority)
@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;
}
UserRepository
@Repository
public interface UserRepository extends JpaRepository<UserEntity, Integer> {
UserEntity findOneByUsernameAndEnabledTrue(String username);
}
UserService
@Service
public class UserService {
private UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public UserEntity loadUserByUsername(String username) {
return userRepository.findOneByUsernameAndEnabledTrue(username);
}
}
SecurityService (extends UserDetailService)
@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;
}
}
SecurityConfig (extends WebSecurityConfigurerAdapter)
@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);
}
}
index.html (using thymeleaf-extras-springsecurity)
<!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>
The Problem
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.
Questions
- Is there any way to make
sec:authentication="principal.phone"work well? (or"princiapl.getPhone()"respectively) - If not, can I get current user's phone number in my thymeleaf without passing it through controller?
- If not, how can I pass current user's
UserEntryobject without plugging model explicitly for instance throughmavof 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?
Thanks!
If my questions are too vague, let me know so then I would update this more concrete.