前面章節(jié)中用戶名、密碼、權(quán)限都是寫(xiě)在配置文件里的,不能動(dòng)態(tài)的管理用戶的權(quán)限,大多數(shù)時(shí)候顯然是不行的。這里介紹從其他數(shù)據(jù)源讀取用戶的信息,例如從數(shù)據(jù)庫(kù),LDAP 等。只需要給 authentication-provider 提供接口 UserDetailsService 的實(shí)現(xiàn)類即可,使用這個(gè)類獲取用戶的信息,涉及以下內(nèi)容:
- 修改 spring-security.xml 中的 authentication-provider
- 類 UserDetailsService 實(shí)現(xiàn)了 Spring Security 的接口 UserDetailsService
- 類 User
- 類 UserService
spring-security.xml
修改 authentication-manager 下的 user-service-ref 為我們自定義的 UserDetailsService
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<beans:bean id="loginSuccessHandler" class="com.xtuer.security.LoginSuccessHandler"/>
<http security="none" pattern="/static/**"/>
<http auto-config="true">
<intercept-url pattern="/admin" access="hasRole('ADMIN')"/>
<intercept-url pattern="/login" access="permitAll"/>
<form-login login-page="/login"
login-processing-url="/login"
default-target-url ="/"
authentication-success-handler-ref="loginSuccessHandler"
authentication-failure-url="/login?error"
username-parameter="username"
password-parameter="password"/>
<access-denied-handler error-page="/deny" />
<logout logout-url="/logout" logout-success-url="/login?logout" />
<csrf disabled="true"/>
</http>
<beans:bean id="userDetailsService" class="com.xtuer.security.UserDetailsService"/>
<authentication-manager>
<authentication-provider user-service-ref="userDetailsService"/>
</authentication-manager>
</beans:beans>
UserDetailsService
UserDetailsService 的作用是根據(jù)登錄表單中用戶的用戶名查找用戶信息用于身份認(rèn)證。Spring Security 中授權(quán)分 2 步:身份驗(yàn)證 (Authentication),權(quán)限驗(yàn)證 (Authorization)
- 用戶在登錄頁(yè)面輸入 username, password,然后點(diǎn)擊登錄按鈕
- 方法 loadUserByUsername() 使用 username 查找到用戶的信息,如密碼,權(quán)限等
- Spring Security 使用查找到的密碼和加密后用戶輸入的密碼進(jìn)行比較,如果相等,則身份驗(yàn)證成功
- 身份驗(yàn)證成功后,使用用戶的權(quán)限和頁(yè)面的訪問(wèn)權(quán)限比較,頁(yè)面的權(quán)限配置在 intercept-url
package com.xtuer.security;
import com.xtuer.bean.User;
import com.xtuer.service.UserService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
public class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {
private UserService userService = new UserService();
/**
* 使用 username 加載用戶的信息,如密碼,權(quán)限等
* @param username 登陸表單中用戶輸入的用戶名
* @return 返回查找到的用戶對(duì)象
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.findUserByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(username + " not found!");
}
return user;
}
}
UserService
UserService 用戶查找用戶信息
package com.xtuer.service;
import com.xtuer.bean.User;
import java.util.HashMap;
import java.util.Map;
public class UserService {
private static Map<String, User> users = new HashMap<String, User>();
static {
// 模擬數(shù)據(jù)源,可以是多種,如數(shù)據(jù)庫(kù),LDAP,從配置文件讀取等
users.put("admin", new User("admin", "{noop}Passw0rd", "ROLE_ADMIN"));
users.put("alice", new User("alice", "{noop}Passw0rd", "ROLE_USER"));
}
public User findUserByUsername(String username) {
return users.get(username);
}
}
User
package com.xtuer.bean;
import com.alibaba.fastjson.JSON;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import java.util.*;
/**
* 用戶類型,根據(jù) userdetails.User 的設(shè)計(jì),roles, authorities, expired 等狀態(tài)不能修改,
* 只能是創(chuàng)建用戶對(duì)象的時(shí)候傳入進(jìn)來(lái)。
*/
@Getter
@Setter
public class User extends org.springframework.security.core.userdetails.User {
private Long id;
private String username;
private String password;
private String mail;
private boolean enabled;
private Set<String> roles = new HashSet<>(); // 用戶的角色
public User() {
// 父類不允許空的用戶名、密碼和權(quán)限,所以給個(gè)默認(rèn)的,這樣就可以用默認(rèn)的構(gòu)造函數(shù)創(chuàng)建 User 對(duì)象。
super("non-exist-username", "", new HashSet<>());
}
/**
* 使用賬號(hào)、密碼、角色創(chuàng)建用戶
*
* @param username 賬號(hào)
* @param password 密碼
* @param roles 角色
*/
public User(String username, String password, String... roles) {
this(username, password, true, roles);
}
/**
* 使用賬號(hào)、密碼、是否禁用、角色創(chuàng)建用戶
*
* @param username 賬號(hào)
* @param password 密碼
* @param enabled 是否禁用
* @param roles 角色
*/
public User(String username, String password, boolean enabled, String... roles) {
super(username, password, enabled, true, true, true, AuthorityUtils.createAuthorityList(roles));
this.username = username;
this.password = password;
this.enabled = enabled;
this.roles.addAll(Arrays.asList(roles));
}
/**
* 用戶信息修改后,例如角色修改后不會(huì)更新到父類的 authorities 中,需要重新創(chuàng)建一個(gè)用戶對(duì)象才行
*
* @param user 已有用戶對(duì)象
* @return 新的用戶對(duì)象,權(quán)限等信息更新到了父類的 authorities 中
*/
public static User userForSpringSecurity(User user) {
return new User(user.username, user.password, user.enabled, user.getRoles().toArray(new String[0]));
}
public static void main(String[] args) {
User user1 = new User();
System.out.println(JSON.toJSONString(user1));
System.out.println(user1.getRoles());
User user2 = new User("Bob", "Passw0rd", "ROLE_USER", "ROLE_ADMIN");
System.out.println(JSON.toJSONString(user2));
System.out.println(user2.getRoles());
}
}
測(cè)試
訪問(wèn) http://localhost:8080/hello
訪問(wèn) http://localhost:8080/admin
輸入錯(cuò)誤的用戶名或密碼,觀察登陸失敗的頁(yè)面
輸入正確的用戶名和密碼,繼續(xù)登陸
訪問(wèn) http://localhost:8080/logout,觀察注銷成功的頁(yè)面