簡介
- UserDetails => Spring Security基礎接口,包含某個用戶的賬號,密碼,權限,狀態(是否鎖定)等信息。只有getter方法。
- Authentication => 認證對象,認證開始時創建,認證成功后存儲於SecurityContext
- principal => 用戶信息對象,是一個Object,通常可轉為UserDetails
UserDetails接口
用於表示一個principal,但是一般情況下是作為(你所使用的用戶數據庫)和(Spring Security 的安全上下文需要保留的信息)之間的適配器。
實際上就是相當於定義一個規范,Security這個框架不管你的應用時怎么存儲用戶和權限信息的。只要你取出來的時候把它包裝成一個UserDetails對象給我用就可以了。
package org.springframework.security.core.userdetails; import java.io.Serializable; import java.util.Collection; import org.springframework.security.core.GrantedAuthority; public interface UserDetails extends Serializable { Collection<? extends GrantedAuthority> getAuthorities(); String getPassword(); String getUsername(); boolean isAccountNonExpired(); boolean isAccountNonLocked(); boolean isCredentialsNonExpired(); boolean isEnabled(); }
UserDetails用來做什么?為什么還要帶上權限集合?
如果我們不用認證框架,我們是怎么手動實現登錄認證的?
基本上就是根據前端提交上來的用戶名從數據庫中查找這個賬號的信息,然后比對密碼。再進一步,可能還會添加一個字段來判斷,當前用戶是否已被鎖定。這個接口就是這么用的。即把這些信息取出來,然后包裝成一個對象交由框架去認證。
為什么還要帶上權限?
因為登錄成功后也不是什么都能訪問的,還要根據你所擁有的權限進行判斷。有權限你才能訪問特定的對象。Security框架是這樣設計的,即認證成功后,就把用戶信息和擁有的權限都存儲在SecurityContext中,當訪問受保護資源(某個對象/方法)的時候,就把權限拿出來比對,看看是否滿足。
框架提供的UserDetails默認實現
public class User implements UserDetails, CredentialsContainer { private static final long serialVersionUID = 500L; private static final Log logger = LogFactory.getLog(User.class); private String password; private final String username; private final Set<GrantedAuthority> authorities; private final boolean accountNonExpired; private final boolean accountNonLocked; private final boolean credentialsNonExpired; private final boolean enabled; public User(String username, String password, Collection<? extends GrantedAuthority> authorities) { this(username, password, true, true, true, true, authorities); } public User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) { if (username != null && !"".equals(username) && password != null) { this.username = username; this.password = password; this.enabled = enabled; this.accountNonExpired = accountNonExpired; this.credentialsNonExpired = credentialsNonExpired; this.accountNonLocked = accountNonLocked; this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities)); } else { throw new IllegalArgumentException("Cannot pass null or empty values to constructor"); } } //省略部分代碼 }
什么時候提供UserDetails信息,怎么提供?
UserDetailsService接口
那肯定是認證的時候。其實認證的操作,框架都已經幫你實現了,它所需要的只是,你給我提供獲取信息的方式。所以它就定義一個接口,然后讓你去實現,實現好了之后再注入給它。
框架提供一個UserDetailsService接口用來加載用戶信息。如果要自定義實現的話,用戶可以實現一個CustomUserDetailsService的類,然后把你的應用中的UserService和AuthorityService注入到這個類中,用戶獲取用戶信息和權限信息,然后在loadUserByUsername方法中,構造一個User對象(框架的類)返回即可。
package org.springframework.security.core.userdetails; public interface UserDetailsService { UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException; }
框架提供的UserDetailsService接口默認實現
- InMemoryDaoImpl => 存儲於內存
- JdbcDaoImpl => 存儲於數據庫(磁盤)
其中,如果你的數據庫設計符合JdbcDaoImpl中的規范,你也就不用自己去實現UserDetailsService了。但是大多數情況是不符合的,因為你用戶表不一定就叫users,可能還有其他前綴什么的,比如叫tb_users。或者字段名也跟它不一樣。如果你一定要使用這個JdbcDaoImpl,你可以通過它的setter方法修改它的數據庫查詢語句。
注:它是利用Spring框架的JdbcTemplate來查詢數據庫的
public class JdbcDaoImpl extends JdbcDaoSupport implements UserDetailsService, MessageSourceAware { public static final String DEF_USERS_BY_USERNAME_QUERY = "select username,password,enabled from users where username = ?"; public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY = "select username,authority from authorities where username = ?"; public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY = "select g.id, g.group_name, ga.authority from groups g, group_members gm, group_authorities ga where gm.username = ? and g.id = ga.group_id and g.id = gm.group_id"; protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); private String authoritiesByUsernameQuery = "select username,authority from authorities where username = ?"; private String groupAuthoritiesByUsernameQuery = "select g.id, g.group_name, ga.authority from groups g, group_members gm, group_authorities ga where gm.username = ? and g.id = ga.group_id and g.id = gm.group_id"; private String usersByUsernameQuery = "select username,password,enabled from users where username = ?"; private String rolePrefix = ""; private boolean usernameBasedPrimaryKey = true; private boolean enableAuthorities = true; private boolean enableGroups; //省略方法 }
注入到哪里去呢?
那肯定是注入到認證處理類中的,框架利用AuthenticationManager(接口)來進行認證。而Security為了支持多種方式認證,它提供ProviderManager類,這個實現了AuthenticationManager接口。它擁有多種認證方式,可以根據認證的類型委托給對應的認證處理類進行處理,這個處理類實現了AuthenticationProvider接口。