Spring Security Custom Login (I)
在剛剛我們說明了如何使用最簡易的登入,但是這些密碼都是明碼,更何況並沒有使用DB來做存取,也代表是沒辦法動態新增使用者的,因此我們需要讓Spring也使用我們自己的UserTable。
Entity
既然都要使用自己的table進行登入,免不了就是要設定一些東西,如dao類的。
User
@Entity
@Table(name="WEB_USERS")
public class User {
@Id
@Column(name="USER_ID")
private String userId;
@Column(name="USER_NAME")
private String userName;
@Column(name="PASSWORD")
private String password;
@OneToMany(fetch=FetchType.EAGER)
@JoinColumn(name="USER_ID")
private Set<UserRole> userRoles;
public Set<UserRole> getUserRoles() {
return userRoles;
}
public void setUserRoles(Set<UserRole> userRoles) {
this.userRoles = userRoles;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User [userId=" + userId + ", userName=" + userName + ", password=" + password
+ ", userRoles=" + userRoles + "]";
}
}
UserRole
@Entity
@Table(name="WEB_USER_ROLES")
public class UserRole {
@Id
@Column(name="ROLE_ID")
private String roleId;
@Column(name="ROLE")
private String role;
@Column(name="USER_ID")
private String userId;
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getRoleId() {
return roleId;
}
public void setRoleId(String roleId) {
this.roleId = roleId;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
@Override
public String toString() {
return "UserRole [roleId=" + roleId + ", role=" + role + "]";
}
}
Dao
UserDao
@Repository
public interface UserDao extends JpaRepository<User, String>{
@Query("select u from User u where u.userName=?1")
User getUserByUserName(String userName);
}
UserRoleDao
@Repository
public interface UserRoleDao extends JpaRepository<UserRole, String>{
}
Security Config
那麼這邊我們需要新增以下幾種
- DataSource 不想解釋
- PasswordEncoder 密碼加密方式,這邊我使用BCryptPasswordEncoder,內部實現方式為Hash + Salt,但我覺得可以研究一下使用自定義的加密算法,每個版本加密算法所產生的編碼可能都不大相同,如果使用官方的算法,很有可能被綁住。
- AuthenticationProvider 原先的認證已經不適合我們,因此我們需要定義一個
- UserDetailsService 取得使用者的方式,我們需要重寫一個
PS. dataSource使用@Autowired方式取得,我設定在HibernateConfig裡面,在Spring with ORM Frameworks章節中可以看見。
package com.mvc.example.webconfig;
import javax.sql.DataSource;
import org.dom4j.swing.BranchTreeNode;
import org.hibernate.boot.registry.selector.SimpleStrategyRegistrationImpl;
import org.hibernate.loader.plan.exec.process.spi.ReturnReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.StandardPasswordEncoder;
import com.mvc.example.service.CustomUserDetailsService;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
auth.userDetailsService(userDetailsService());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService());
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
return daoAuthenticationProvider;
}
@Bean(name="userDetailsService")
public UserDetailsService userDetailsService() {
return new CustomUserDetailsService();
}
@Bean
public SessionRegistry sessionRegistry() {
SessionRegistry sessionRegistry = new SessionRegistryImpl();
return sessionRegistry;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/login").permitAll() //全部的人都可以嘗試登入
.and()
.formLogin() //這邊如果沒有使用下面的設定,Spring會自行產生簡易的登入頁面
.loginPage("/login") //登入頁面位置
.usernameParameter("username") //表單帳號參數名稱
.passwordParameter("password") //表單密碼參數名稱
.defaultSuccessUrl("/") //登入完成後導引至首頁
.permitAll() //此頁面沒有權限限制
.and()
.logout()
.logoutSuccessUrl("/login") //登出後跳轉至登入頁面
.invalidateHttpSession(true) //使該Session無效
.and()
.csrf() //使用csrf防禦機制
.and()
.sessionManagement() //管理Session
.maximumSessions(1) //同時只能一個Session
.maxSessionsPreventsLogin(true) //只能讓一個帳號存在一個Session
.sessionRegistry(sessionRegistry()) //監聽Session的欄位,基本上預設即可,若要做更多用途可以自行撰寫
.and()
.and()
.exceptionHandling().accessDeniedPage("/403"); //將所有403頁面導向這裡
}
}
CustomUserDetailsService
剛剛我們已經說要重寫這個類別,主因是我們需要提供認證方式,那再讓我們看看如何使用
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserDao userDao;
//登入時會使用這個方法取得資料,傳入參數為帳號,若確認有此帳號,會建立一個User物件,後續驗證會使用。
@Override
public UserDetails loadUserByUsername(String arg0) throws UsernameNotFoundException {
User user = userDao.getUserByUserName(arg0);
if (user == null) {
throw new UsernameNotFoundException("user not found");
}
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for(UserRole role : user.getUserRoles()) {
authorities.add(new SimpleGrantedAuthority(role.getRole()));
}
return new org.springframework.security.core.userdetails.User(user.getUserName(), user.getPassword(), authorities);
}
}