【权限管理平台】项目开发Day—03
🏡 ?博客首页:派 大 星
?? ?欢迎关注 ????点赞 ?🎒?收藏 ????留言
🎢 ?本文由派大星原创编撰
🚧 ?系列专栏:项目从0搭建
🎈 ?本系列项目从设计到实现源码全部开源免费学习使用,一起追向理想,欢迎各位大佬监督打卡开发!
🍉?难度分析
????????说起登录功能的实现可以实现的相当简单,简单到只需要让用户输入用户名密码就可以完成登录操作,但是网站的安全性无法得到保证??;当然也可以实现的相当麻烦,加上各种外部校验,例如:验证码 、选字 、加减乘除 ,这样既可以美化 我们的登录功能,同时又可以提高网站的安全性 ??
🍒?技术介绍
????????本项目的登录功能我采用的是后面比较复杂的实现方式,由于用户在登录后,需要将该用户所携带的权限以及角色信息与用户紧贴 ,所以,这部分我采用的是将用户信息以及权限信息放入jwt 中的荷载(Payload ) 中,通过Shiro 自带的Realm中的授权机制进行用户与权限间的紧贴 实现。同时也给登录功能添加了输入项——验证码 ,数据库对密码进行MD5 二次加密入库处理,对平台的安全性进一步的提高。
🍑?集成验证码
引入依赖
<dependency>
<groupId>com.github.axet</groupId>
<artifactId>kaptcha</artifactId>
<version>0.0.9</version>
</dependency>
????????在集成验证码功能时需要配置关于它的属性,例如:生成的验证码是否有边框、边框的颜色、字体的颜色、大小等等属性!该配置类必须需要,不然启动项目会报错??
验证码配置
@Configuration
public class CaptchaConfig {
@Bean
public DefaultKaptcha getDefaultKaptcha(){
DefaultKaptcha defaultKaptcha=new DefaultKaptcha();
Properties properties = new Properties();
properties.setProperty("kaptcha.border", "yes");
properties.setProperty("kaptcha.border.color", "105,179,90");
properties.setProperty("kaptcha.session.key","code");
properties.setProperty("kaptcha.textproducer.font.color", "blue");
properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
properties.setProperty("kaptcha.textproducer.font.size", "30");
properties.setProperty("kaptcha.textproducer.char.length", "4");
properties.setProperty("kaptcha.textproducer.char.space", "4");
properties.setProperty("kaptcha.image.width", "100");
properties.setProperty("kaptcha.image.height", "40");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
????????但是生成的验证码我们又该如何去获取呢?两种方案:第一种存入Session中,在业务逻辑中采用request.getSession.setAttribute()/request.getSession.getAttribute() 先存后取,当我们取出来之后可以调用 removeAttribute() 对存入的验证码进行删除操作;第二种是将生成的验证码存储到Redis 中的,在业逻辑中使用set()/get() 方法对存入的验证码进行存取,但是使用Redis的好处是在存储的同时可以对指定的key 设置过期时间,这样就可以不用再调用API 进行删除操作。
生成验证码
@Autowired
private DefaultKaptcha defaultKaptcha;
@Autowired
private RedisService redisService;
@ApiOperation(value = "验证码")
@GetMapping(value = "/captcha",produces = "image/jepg")
public void captcha(HttpServletRequest request, HttpServletResponse response) throws Exception{
response.setDateHeader("Expires", 0);
response.setHeader("Cache-Control", "no-store, no-cache, mustrevalidate");
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
response.setHeader("Pragma", "no-cache");
response.setContentType("image/jpeg");
String text = defaultKaptcha.createText();
System.out.println("验证码内容:"+text);
redisService.set("captcha",text,60, TimeUnit.SECONDS);
BufferedImage image = defaultKaptcha.createImage(text);
ServletOutputStream outputStream = null;
try {
outputStream = response.getOutputStream();
ImageIO.write(image,"jpg",outputStream);
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (outputStream != null){
outputStream.close();
}
}
}
使用Swagger进行接口测试
测试成功?? 使用验证码可以降低被脚本暴力破解的风险!在图中在加入些许干扰竖线,使其破解脚本不能直接读取验证码中的内容。
🍇?登录功能集成
前提:
????????在实现登录功能时,我们需要用户在前端传递的用户名、密码以及验证码信息来进行校验【数据校验需要前后端共同参与才会使网站的安全性更高】但是这些信息部分是用户表中的信息,部分又不属于,遇到这种情况,通常需要我们对需要校验的数据进行二次封装,从而达到对代码的复用以及解耦。
数据封装
@Data
public class LoginVo {
@ApiModelProperty(value = "用户账号")
@NotBlank(message = "用户名不能为空")
private String username;
@ApiModelProperty(value = "用户密码")
@NotBlank(message = "密码不能为空")
private String password;
@ApiModelProperty(value = "验证码")
@NotBlank(message = "验证码不能为空")
private String captcha;
}
????????代码中的@NotBlank(message="xxx") 是一种注解式校验。比如我们判断字符串是否为空会选择在业务逻辑中使用StringUtils.isEmpty() 等方法来进行判断,如果为空则抛出异常,但是如果我们需要校验的字段非常多,那么使用这种方式是完全不可取的,代码的冗余度过高。但是注解式校验的就解决了这方面的问题,使得我们的业务逻辑更简洁、易懂。??????注意: 如果属性使用注解式校验的话,接口处传出对应参数对象时需要添加注解@Valid 。
登录功能的具体实现逻辑
@Override
public RespBody login(LoginVo vo) {
String code = (String) redisService.get("captcha");
if (!redisService.hasKey("captcha")){
throw new BusinessException(BaseResponseCode.KEY_ALREADY_EXPIRED);
}
if (!vo.getCaptcha().equalsIgnoreCase(code)){
throw new BusinessException(BaseResponseCode.CAPTCHA_ERROR);
}
SysUser user = userMapper.getUserByName(vo.getUsername());
if (user == null){
throw new BusinessException(BaseResponseCode.ACCOUNT_ERROR);
}
if (user.getStatus() == 2){
throw new BusinessException(BaseResponseCode.ACCOUNT_LOCKED);
}
if (!PasswordUtils.matches(user.getSalt(),vo.getPassword(),user.getPassword())){
throw new BusinessException(BaseResponseCode.ACCOUNT_PASSWORD_ERROR);
}
RespBody body = new RespBody();
body.setId(user.getId());
body.setUsername(user.getUsername());
Map<String,Object> claims = new HashMap<>();
claims.put(Constant.ROLES_INFOS_KEY,getRoleByUserId(user.getId()));
claims.put(Constant.PERMISSIONS_INFOS_KEY,getPermissionByUserId(user.getId()));
claims.put(Constant.JWT_USER_NAME,user.getUsername());
String accessToken = JwtTokenUtil.getAccessToken(user.getId(), claims);
body.setAccessToken(accessToken);
return body;
}
测试接口
测试成功????
今天的内容到这里就结束了,希望可以帮助到各位路过的小伙伴!
新鲜出炉的代码将会及时更新到Gitee 仓库
以上代码属于部分实现,想要了解全部配置请移步 派大星的Gitee仓库
今天的工作就进行到这里!希望大佬们可以监督派大星一步步从0搭建该平台!
|