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>
数据库结构
其余的mapper层接口和service层接口,这里就不附加上了,以上这些就是,我自己找的资料,总结后实现了简单的登录和注册,以及加密的功能,如有什么问题,可以在评论区指出。
上一篇: 咖喱是什么味道?吃咖喱要注意什么?
下一篇: 折半查找原理(C++)