欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

springboot+shiro 实现登录、注册和密码加密(vue+springboot前后端分离)

程序员文章站 2022-03-15 11:41:23
...

首先引入依赖

		<dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.30</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.5.2</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.21</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.45</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

附:数据库配置和log4j在控制台输出日志的配置,在application.properties,加上:

# LOG4J配置
log4j.rootCategory=INFO, stdout
# 控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender 
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L - %m%n

mybatis.mapper-locations=classpath:mapper/*.xml

#别名
mybatis.type-aliases-package=com.sys.domain
# 数据库配置
spring.datasource.driver-class-name =com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/sys?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.username =root
spring.datasource.password =root
#如果不配置阿里的druid,会自动使用默认的数据源 (com.zaxxer.hikari.HikariDataSource)
spring.datasource.type = com.alibaba.druid.pool.DruidDataSource

自定义类ShiroConfig ,配置shiro

/**
 * shiro配置类
 * @author hp
 */
@Configuration
public class ShiroConfig {

    @Bean(name = "factoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        factoryBean.setSecurityManager(securityManager);

        //添加shiro的内置过滤器
        /*
            anon:无需认证就可以访问
            authc:必须认证了才能访问
            user:必须拥有 记住我 功能才能使用
            perms:拥有对某个资源的权限才能访问
            role:拥有某个角色权限才能访问
         */
        Map<String, String> filterMap = new LinkedHashMap<>();


        factoryBean.setFilterChainDefinitionMap(filterMap);

        return factoryBean;
    }

    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm")UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联userRealm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    @Bean
    public UserRealm userRealm(@Qualifier("matcher") HashedCredentialsMatcher matcher){
        UserRealm userRealm = new UserRealm();
        userRealm.setAuthorizationCachingEnabled(false);
        userRealm.setCredentialsMatcher(matcher);
        return userRealm;
    }

    /**
     * 密码匹配凭证管理器
     *
     * @return
     */
    @Bean(name = "matcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 采用MD5方式加密
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");
        // 设置加密次数
        hashedCredentialsMatcher.setHashIterations(1024);
        hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
        return hashedCredentialsMatcher;
    }
}

自定义类UserRealm 类,用于配置shiro的授权与认证

public class UserRealm extends AuthorizingRealm {
    @Autowired
    UserService userService;

	/**
	* 授权
	*/
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("doGetAuthorizationInfo=>进行了授权逻辑");
        return null;
    }

	/**
	* 认证
	*/
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("doGetAuthenticationInfo=>进行了认证逻辑");
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
        User user = userService.findByName(userToken.getUsername());
        //当前realm对象的name
        String realmName = getName();
        //盐值
        ByteSource credentialsSalt = ByteSource.Util.bytes(user.getSalt());
        if (user == null) {
            //抛出异常  UnknownAccountException
            return null;
        }

        return new SimpleAuthenticationInfo(user,user.getPwd(),credentialsSalt, realmName);
    }
}

doGetAuthenticationInfo 方法中,从token中获取user对象后,从user对象中取出存在数据库中的 salt =>盐值,在登陆时,会拿到数据库中的密码,与该账户所存储的盐值,交给shiro管理。

SimpleAuthenticationInfo(
user, //用户对象
user.getPwd(),  // 用户的密码
credentialsSalt, //盐值
realmName   //当前realm对象的name
);

用户登录

 /**
     * 登录
     *
     * @param user
     * @return
     */
    @RequestMapping("/login")
    public ResultUtil login(User user) {

        //获取当前用户
        Subject subject = SecurityUtils.getSubject();
         //将用户输入的用户名写密码封装到一个UsernamePasswordToken对象中
        UsernamePasswordToken token = new UsernamePasswordToken(user.getAccount(), user.getPwd());
        
		//用Subject对象执行登录方法,没有抛出任何异常说明登录成功
        try {
            //执行登录方法,如果没有异常,就OK
            //login()方法会调用 Realm类中执行认证逻辑的方法,
            //并将这个参数传递给doGetAuthenticationInfo()方法
            subject.login(token);
            return new ResultUtil(0, token, "登录成功!");
        } catch (UnknownAccountException e) {
            return new ResultUtil(-1, null, "账号错误!");
        } catch (IncorrectCredentialsException e) {
            return new ResultUtil(-1, null, "密码错误!");
        }
    }

用户注册

/**
     * 注册
     *
     * @param user
     * @return
     */
    @RequestMapping("/register")
    public ResultUtil register(User user) {
        String msg = userService.addUser(user);
        if ("134".equals(msg)){
            return new ResultUtil(-1,null,"该账户已被注册!");
        }else if ("135".equals(msg)){
            return new ResultUtil(-1,null,"该昵称已存在!");
        }else if ("502".equals(msg)){
            return new ResultUtil(-1,null,"注册失败!");
        }else if ("200".equals(msg)){
            return new ResultUtil(0,null,"注册成功!");
        }else {
            return new ResultUtil(-1,null,"未知错误!");
        }
    }
注册的服务接口实现类
@Override
    public String addUser(User user) {
        //查询注册的用户名,在数据库中是否存在,如果查询到的信息为空,则注册添加用户信息
        User info = userMapper.findByName(user.getAccount());
        //查询注册的昵称,在数据库中是否存在
        User byNickName = userMapper.findByNickName(user.getNickname());
        if(info != null){
            return "134";
        }else if(byNickName != null){
            return "135";
        }else{
            try {
                //随机生成UUID,作为盐值
                String uuid = UUID.randomUUID().toString().replace("-", "");
                //调用MD5_Shiro工具类,生成加密后的密码
                String newPwd = MD5_Shiro.encryptPassword("MD5",user.getPwd(),uuid,1024);
                user.setPwd(newPwd);
                user.setSalt(uuid);
                userMapper.addUser(user);
                return "200";
            } catch (Exception e) {
                e.printStackTrace();
                return "502";
            }
        }
    }

在用户注册的时候,会从数据库中查找 账号和使用的昵称 是否已存在,保持账号和昵称,是唯一的;
加密使用的盐值,使用了UUID随机生成的,然后在注册存入数据库时,一同存入;

MD5_Shiro.encryptPassword(
"MD5",  //使用MD5加密方式
user.getPwd(),    //用户输入的密码
uuid,             //生成的UUID编码,作为加密使用的盐值
1024              //加密次数
);

MD5_Shiro工具类

public class MD5_Shiro {
    /**
     * 随机生成 salt 需要指定 它的字符串的长度
     *
     * @param len 字符串的长度
     * @return salt
     */
    public static String generateSalt(int len) {
        //一个Byte占两个字节
        int byteLen = len >> 1;
        SecureRandomNumberGenerator secureRandom = new SecureRandomNumberGenerator();
        return secureRandom.nextBytes(byteLen).toHex();
    }

    /**
     * 获取加密后的密码,使用默认hash迭代的次数 1 次
     *
     * @param hashAlgorithm hash算法名称 MD2、MD5、SHA-1、SHA-256、SHA-384、SHA-512、etc。
     * @param password      需要加密的密码
     * @param salt          盐
     * @return 加密后的密码
     */
    public static String encryptPassword(String hashAlgorithm, String password, String salt) {
        return encryptPassword(hashAlgorithm, password, salt, 1);
    }

    /**
     * 获取加密后的密码,需要指定 hash迭代的次数
     *
     * @param hashAlgorithm  hash算法名称 MD2、MD5、SHA-1、SHA-256、SHA-384、SHA-512、etc。
     * @param password       需要加密的密码
     * @param salt           盐
     * @param hashIterations hash迭代的次数
     * @return 加密后的密码
     */
    public static String encryptPassword(String hashAlgorithm, String password, String salt, int hashIterations) {
        SimpleHash hash = new SimpleHash(hashAlgorithm, password, salt, hashIterations);
        return hash.toString();
    }
}

User实体类

@Data
public class User {
    private Integer uId;
    private String account;
    private String pwd;
    private String nickname;
    private String salt;
    private Date createTime;
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.sys.mapper.UserMapper">
    <resultMap id="user-map" type="user">
        <id column="u_id" property="uId"/>
        <result column="account" property="account"/>
        <result column="pwd" property="pwd"/>
        <result column="nickname" property="nickname"/>
        <result column="salt" property="salt"/>
        <result column="create_time" property="createTime"/>
    </resultMap>

    <select id="findByName" parameterType="String" resultMap="user-map">
        select * from user where account = #{account}
    </select>

    <select id="findByNickName" parameterType="String" resultType="user">
        select * from user where nickname = #{nickname}
    </select>

    <insert id="addUser" parameterType="user">
        insert into user(account, pwd, nickname, salt) values (#{account},#{pwd},#{nickname},#{salt})
    </insert>
</mapper>

数据库结构

springboot+shiro 实现登录、注册和密码加密(vue+springboot前后端分离)

其余的mapper层接口和service层接口,这里就不附加上了,以上这些就是,我自己找的资料,总结后实现了简单的登录和注册,以及加密的功能,如有什么问题,可以在评论区指出。

相关标签: spring框架