spring security配置文件
spring security的用戶信息從數據庫中查詢:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:secu="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<!--靜態資源不需要認證-->
<security:http pattern="/css/**" security="none"/>
<security:http pattern="/img/**" security="none"/>
<security:http pattern="/plugins/**" security="none"/>
<!--
auto-config:表示是否自動加載springSecurity的配置文件
use-expressions 表示是否使用spring的el表達式來配置springSecurity
-->
<security:http auto-config="true" use-expressions="true">
<!--認證頁面可以匿名訪問-->
<security:intercept-url pattern="/login.jsp" access="permitAll()"/>
<!--攔截資源-->
<!--
access="hasAnyRole('ROLE_USER') 表示只有ROLE_USER角色才能訪問資源
-->
<security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER')"/>
<security:form-login login-page="/login.jsp"
login-processing-url="/login"
default-target-url="/index.jsp"
authentication-failure-url="/failer.jsp"/>
<!--配置退出登錄信息-->
<security:logout logout-url="/logout" logout-success-url="/login.jsp"/>
<!--去掉csrf攔截-->
<security:csrf disabled="false"/>
</security:http>
<!--設置springSecurity的認證用戶信息的來源-->
<security:authentication-manager>
<security:authentication-provider user-service-ref="userServiceImpl">
</security:authentication-provider>
</security:authentication-manager>
</beans>
認證
先寫一個service去繼承UserDetailsService接口,在去實現方法:
UserService接口:
public interface UserService extends UserDetailsService {
}
實現:
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Autowired
private RoleService roleService;
/**
* @param username 用戶在瀏覽器中輸入的用戶名
* @return UserDetails 是SpringSecurity自己的用戶對象
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
try{
SysUser sysUser = userDao.findByName(username);
if(Objects.isNull(sysUser))
return null;
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for(SysRole role : sysUser.getRoles()){
authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
}
//{noop}后面的密碼,SpringSecurity會認為是密碼原文
return new User(username,"{noop}"+sysUser.getPassword(),authorities);
}catch (Exception e){
return null;
}
}
}
實現密碼加密
<!--把加密對象放入ioc容器中-->
<bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<security:authentication-manager>
<security:authentication-provider user-service-ref="userServiceImpl">
<security:password-encoder ref="passwordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>
認證的時候,把“{noop}”去掉
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
try{
SysUser sysUser = userDao.findByName(username);
if(Objects.isNull(sysUser))
return null;
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for(SysRole role : sysUser.getRoles()){
authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
}
//{noop}后面的密碼,SpringSecurity會認為是原文
return new User(username,sysUser.getPassword(),authorities);
}catch (Exception e){
return null;
}
}
用戶狀態
User對象還有另一個構造方法,含有四個布爾值,用於存取用戶的狀態。
只有四個布爾值都為true時,才能登陸成功,否則失敗。
/**
* Construct the <code>User</code> with the details required by
* {@link org.springframework.security.authentication.dao.DaoAuthenticationProvider}.
*
* @param username the username presented to the
* <code>DaoAuthenticationProvider</code>
* @param password the password that should be presented to the
* <code>DaoAuthenticationProvider</code>
* @param enabled set to <code>true</code> if the user is enabled
* @param accountNonExpired set to <code>true</code> if the account has not expired
* @param credentialsNonExpired set to <code>true</code> if the credentials have not
* expired
* @param accountNonLocked set to <code>true</code> if the account is not locked
* @param authorities the authorities that should be granted to the caller if they
* presented the correct username and password and the user is enabled. Not null.
*
* @throws IllegalArgumentException if a <code>null</code> value was passed either as
* a parameter or as an element in the <code>GrantedAuthority</code> collection
*/
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)) {
throw new IllegalArgumentException(
"Cannot pass null or empty values to constructor");
}
this.username = username;
this.password = password;
this.enabled = enabled;
this.accountNonExpired = accountNonExpired;
this.credentialsNonExpired = credentialsNonExpired;
this.accountNonLocked = accountNonLocked;
this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
}
remember me
一般網站都提供了記住我的功能,如圖所示:
SpringSecurity也有對應的功能,在AbstractRememberMeServices類中判斷的。
protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
if (alwaysRemember) {
return true;
}
String paramValue = request.getParameter(parameter);
if (paramValue != null) {
if (paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on")
|| paramValue.equalsIgnoreCase("yes") || paramValue.equals("1")) {
return true;
}
}
if (logger.isDebugEnabled()) {
logger.debug("Did not send remember-me cookie (principal did not set parameter '"
+ parameter + "')");
}
return false;
}
配置:手動開啟remember-me的過濾器
<security:http auto-config="true" use-expressions="true">
<security:remember-me token-validity-seconds="60"/>
</security:http>
登陸后,我們可以在cookie中看到remember-me的相關信息
然后60s后失效。此外手動點擊注銷,也可以讓cookie失效。
持久化remember me信息
因為remember me的信息存儲在瀏覽器端,不安全,所以spring security提供了一種更安全的機制:在客戶端僅僅保存無意義的加密串(與用戶名、密碼等敏感數據無關),然后在數據庫中保存該字符串與用戶的對應關系,自動登錄時,用cookie中的加密串,到db中驗證,如果通過,自動登陸才算通過。
創建一張表,注意表的名稱和字段名稱都不能修改。
CREATE TABLE `persistent_logins` (
`username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`series` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`token` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`last_used` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0),
PRIMARY KEY (`series`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
xml配置:
<security:remember-me data-source-ref="dataSource" token-validity-seconds="60"/>
配置完成后,重啟應用,重新登陸,發現remember-me信息已經被保存到數據庫中: