zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    《20171122-构建之法:现代软件工程-阅读笔记》
    课后作业-阅读任务-阅读提问-5
    结队-贪吃蛇-项目进度
    《团队-爬取豆瓣Top250电影-团队-阶段互评》
    zip相关知识梳理(一)
    C++入职学习篇--代码规范(持续更新)
    C++入职学习篇--新员工入职(持续更新)
    Node.js学习之TCP/IP数据通讯
    Node.js之操作文件系统(二)
    Node.js之操作文件系统(一)
  • 原文地址:https://www.cnblogs.com/wwjj4811/p/13755542.html
Copyright © 2011-2022 走看看