Spring Security:整合数据库


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

一般网站都提供了记住我的功能,如图所示:

image-20200930163849859

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的相关信息

image-20200930165815939

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

image-20200930171211167


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM