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信息已经被保存到数据库中: