博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【项目】springboot中使用kaptcha生成验证码,登录时密码加盐处理
阅读量:3903 次
发布时间:2019-05-23

本文共 21793 字,大约阅读时间需要 72 分钟。

记录使用kaptcha的过程

为前后端分离项目,前端vue框架

文章目录


0.下载jar包

在maven的网站https://mvnrepository.com/search?q=com.github.penggle

搜索下载jar
在这里插入图片描述
导入到工程中

1.添加依赖

com.github.penggle
kaptcha
2.3.2
org.springframework.security
spring-security-config
5.4.1
javax.xml.bind
jaxb-api
2.3.1
com.sun.xml.bind
jaxb-impl
2.1
com.sun.xml.bind
jaxb-core
2.1.14
javax.activation
activation
1.1.1

后面的依赖是因为测试的时候有报错添加的,以防万一可以添加一下

2.添加KaptchaConfig配置

package com.louis.mango.config;import java.util.Properties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import com.google.code.kaptcha.impl.DefaultKaptcha;import com.google.code.kaptcha.util.Config;@Configurationpublic class KaptchaConfig {
@Bean public DefaultKaptcha producer() {
Properties properties = new Properties(); properties.put("kaptcha.border", "no"); properties.put("kaptcha.textproducer.font.color", "black"); properties.put("kaptcha.textproducer.char.space", "5"); Config config = new Config(properties); DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); defaultKaptcha.setConfig(config); return defaultKaptcha; }}

注意注解 @Bean,不添加这个注解的话后面注入时会报无法注入。

3.后端用于登录的封装类编写

LoginBean:

public class LoginBean {
private String account; private String password; private String captcha; public String getAccount() {
return account; } public void setAccount(String account) {
this.account = account; } public String getPassword() {
return password; } public void setPassword(String password) {
this.password = password; } public String getCaptcha() {
return captcha; } public void setCaptcha(String captcha) {
this.captcha = captcha; } }

4.修改controller层代码与前端交互

后端:

@RestControllerpublic class SysLoginController {
@Autowired private Producer producer; @Autowired private SysUserService sysUserService; @Autowired private AuthenticationManager authenticationManager; @GetMapping("captcha.jpg") public void captcha(HttpServletResponse response, HttpServletRequest request) throws ServletException, IOException {
response.setHeader("Cache-Control", "no-store, no-cache"); response.setContentType("image/jpeg"); // 生成文字验证码 String text = producer.createText(); // 生成图片验证码 BufferedImage image = producer.createImage(text); // 保存到验证码到 session request.getSession().setAttribute(Constants.KAPTCHA_SESSION_KEY, text); ServletOutputStream out = response.getOutputStream(); ImageIO.write(image, "jpg", out); IOUtils.closeQuietly(out); } /** * 登录接口 */ @PostMapping(value = "/login") //public HttpResult public String login(@RequestBody LoginBean loginBean, HttpServletRequest request) throws IOException {
String username = loginBean.getAccount(); String password = loginBean.getPassword(); String captcha = loginBean.getCaptcha(); // 从session中获取之前保存的验证码跟前台传来的验证码进行匹配 Object kaptcha = request.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY); if (kaptcha == null) {
//return HttpResult.error("验证码已失效"); return "已经失效"; } if (!captcha.equals(kaptcha)) {
return "验证码不正确"; //return HttpResult.error("验证码不正确"); } // 用户信息 SysUser user = sysUserService.findByName(username); System.out.println(user); // 账号不存在、密码错误 if (user == null) {
//return HttpResult.error("账号不存在"); return "账号不存在"; } if (!PasswordUtils.matches(user.getSalt(), password, user.getPassword())) {
return "密码不正确"; } // 账号锁定 if (user.getStatus() == 0) {
return "账号已被锁定,请联系管理员"; } // 系统登录认证 JwtAuthenticatioToken token = SecurityUtils.login(request, username, password, authenticationManager); System.out.println("yes!"); System.out.println(token);//// return HttpResult.ok(token);// } return "ok"; }}

前端:

template:

script:

export default {
data() {
return {
loading: false, loginForm: {
account: 'admin', password: 'admin', captcha:'', src: '' }, fieldRules: {
account: [ {
required: true, message: '请输入账号', trigger: 'blur' } ], password: [ {
required: true, message: '请输入密码', trigger: 'blur' } ] }, checked: true } }, methods: {
login() {
this.loading = true let userInfo = {
account:this.loginForm.account, password:this.loginForm.password, captcha:this.loginForm.captcha } this.$api.login.login(userInfo).then((res) => {
if (res == "ok" ) {
window.sessionStorage.setItem('flag','ok'); // session 放置--> this.$message.success("登陆成功!!!"); this.$router.push({
path: "/home"}); } this.loading = false }).catch((res) => {
this.$message({
message: res.message, type: 'error' }) }) }, refreshCaptcha: function(){
this.loginForm.src = "http://localhost:8001/captcha.jpg?t=" + new Date().getTime(); }, reset() {
this.$refs.loginForm.resetFields() } mounted() {
this.refreshCaptcha() }}

关于其中api:

目录:

(src下)
在这里插入图片描述
index.js:

import api from './api'const install = Vue => {
if (install.installed) return; install.installed = true; Object.defineProperties(Vue.prototype, {
// 注意,此处挂载在 Vue 原型的 $api 对象上 $api: {
get() {
return api } } })}export default install

login.js:

import axios from '../axios'/*  * 系统登录模块 */// 登录export const login = data => {
return axios({
url: 'login', method: 'post', data })}// 登出export const logout = () => {
return axios({
url: 'logout', method: 'get' })}

axios.js:

import axios from 'axios';import config from './config';import Cookies from "js-cookie";import router from '@/router'export default function $axios(options) {
return new Promise((resolve, reject) => {
const instance = axios.create({
baseURL: config.baseUrl, headers: config.headers, timeout: config.timeout, withCredentials: config.withCredentials }) // request 请求拦截器 instance.interceptors.request.use( config => {
let token = Cookies.get('token') // 发送请求时携带token if (token) {
config.headers.token = token } else {
// 重定向到登录页面 router.push('/login') } return config }, error => {
// 请求发生错误时 console.log('request:', error) // 判断请求超时 if (error.code === 'ECONNABORTED' && error.message.indexOf('timeout') !== -1) {
console.log('timeout请求超时') } // 需要重定向到错误页面 const errorInfo = error.response console.log(errorInfo) if (errorInfo) {
error = errorInfo.data // 页面那边catch的时候就能拿到详细的错误信息,看最下边的Promise.reject const errorStatus = errorInfo.status; // 404 403 500 ... router.push({
path: `/error/${
errorStatus}` }) } return Promise.reject(error) // 在调用的那边可以拿到(catch)你想返回的错误信息 } ) // response 响应拦截器 instance.interceptors.response.use( response => {
return response.data }, err => {
if (err && err.response) {
switch (err.response.status) {
case 400: err.message = '请求错误' break case 401: err.message = '未授权,请登录' break case 403: err.message = '拒绝访问' break case 404: err.message = `请求地址出错: ${
err.response.config.url}` break case 408: err.message = '请求超时' break case 500: err.message = '服务器内部错误' break case 501: err.message = '服务未实现' break case 502: err.message = '网关错误' break case 503: err.message = '服务不可用' break case 504: err.message = '网关超时' break case 505: err.message = 'HTTP版本不受支持' break default: } } console.error(err) return Promise.reject(err) // 返回接口返回的错误信息 } ) // 请求处理 instance(options).then(res => {
resolve(res) return false }).catch(error => {
reject(error) }) })}

在这里插入图片描述

mock目录下的index.js:

import Mock from 'mockjs'import * as login from './modules/login'import * as user from './modules/user'import * as role from './modules/role'import * as dept from './modules/dept'import * as menu from './modules/menu'import * as dict from './modules/dict'import * as config from './modules/config'import * as log from './modules/log'import * as loginlog from './modules/loginlog'// 1. 开启/关闭[所有模块]拦截, 通过调[openMock参数]设置.// 2. 开启/关闭[业务模块]拦截, 通过调用fnCreate方法[isOpen参数]设置.// 3. 开启/关闭[业务模块中某个请求]拦截, 通过函数返回对象中的[isOpen属性]设置.let openMock = true//let openMock = falsefnCreate(user, openMock)fnCreate(role, openMock)fnCreate(dept, openMock)fnCreate(menu, openMock)fnCreate(dict, openMock)fnCreate(config, openMock)fnCreate(log, openMock)fnCreate(loginlog, openMock)fnCreate(login, openMock)/** * 创建mock模拟数据 * @param {*} mod 模块 * @param {*} isOpen 是否开启? */function fnCreate (mod, isOpen = true) {
if (isOpen) {
for (var key in mod) {
((res) => {
if (res.isOpen !== false) {
let url = "http://localhost:8001/" if(!url.endsWith("/")) {
url = url + "/" } url = url + res.url Mock.mock(new RegExp(url), res.type, (opts) => {
opts['data'] = opts.body ? JSON.parse(opts.body) : null delete opts.body console.log('\n') console.log('%cmock拦截, 请求: ', 'color:blue', opts) console.log('%cmock拦截, 响应: ', 'color:blue', res.data) return res.data }) } })(mod[key]() || {
}) } }}

mock目录下的login.js:

/*  * 系统登录模块 */// 登录接口export function login() {
const loginData = {
"code": 200, "msg": null, "data": {
"authorities": [], "details": {
"remoteAddress": "0:0:0:0:0:0:0:1", "sessionId": "E9E774A8EB4405B25692D84B4521CB45" }, "authenticated": false, "principal": "admin", "credentials": "admin", "token": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTU0NzkzMTYxOCwiY3JlYXRlZCI6MTU0Nzg4ODQxODgyMiwiYXV0aG9yaXRpZXMiOlt7ImF1dGhvcml0eSI6InN5czptZW51OmRlbGV0ZSJ9LHsiYXV0aG9yaXR5Ijoic3lzOmRpY3Q6ZWRpdCJ9LHsiYXV0aG9yaXR5Ijoic3lzOmRpY3Q6ZGVsZXRlIn0seyJhdXRob3JpdHkiOiJzeXM6Y29uZmlnOmFkZCJ9LHsiYXV0aG9yaXR5Ijoic3lzOm1lbnU6YWRkIn0seyJhdXRob3JpdHkiOiJzeXM6dXNlcjphZGQifSx7ImF1dGhvcml0eSI6InN5czpkZXB0OmRlbGV0ZSJ9LHsiYXV0aG9yaXR5Ijoic3lzOnJvbGU6ZWRpdCJ9LHsiYXV0aG9yaXR5Ijoic3lzOnJvbGU6dmlldyJ9LHsiYXV0aG9yaXR5Ijoic3lzOmRpY3Q6dmlldyJ9LHsiYXV0aG9yaXR5Ijoic3lzOnVzZXI6ZGVsZXRlIn0seyJhdXRob3JpdHkiOiJzeXM6ZGVwdDp2aWV3In0seyJhdXRob3JpdHkiOiJzeXM6bWVudTp2aWV3In0seyJhdXRob3JpdHkiOiJzeXM6ZGljdDphZGQifSx7ImF1dGhvcml0eSI6InN5czpyb2xlOmFkZCJ9LHsiYXV0aG9yaXR5Ijoic3lzOnVzZXI6dmlldyJ9LHsiYXV0aG9yaXR5Ijoic3lzOmRlcHQ6ZWRpdCJ9LHsiYXV0aG9yaXR5Ijoic3lzOmNvbmZpZzplZGl0In0seyJhdXRob3JpdHkiOiJzeXM6bG9naW5sb2c6dmlldyJ9LHsiYXV0aG9yaXR5Ijoic3lzOnVzZXI6ZWRpdCJ9LHsiYXV0aG9yaXR5Ijoic3lzOmNvbmZpZzp2aWV3In0seyJhdXRob3JpdHkiOiJzeXM6Y29uZmlnOmRlbGV0ZSJ9LHsiYXV0aG9yaXR5Ijoic3lzOmRlcHQ6YWRkIn0seyJhdXRob3JpdHkiOiJzeXM6cm9sZTpkZWxldGUifSx7ImF1dGhvcml0eSI6InN5czptZW51OmVkaXQifV19.Lw2qb2BJHwmiVMHS_vbaLf7vnTT6frr7vTS2-nJ1Lo0uOduqK6nPtBtgEka-fH7ow-s5n7OH1WZkUvH0PN6oyA", "name": "admin" } } return {
url: 'login', type: 'post', data: loginData }}// 登出接口export function logout() {
const logoutData = {
"code": 200, "msg": null, "data": {
} } return {
url: 'logout', type: 'get', data: logoutData }}

5.密码加盐和token相关的代码

目录:

在这里插入图片描述

PasswordUtils :

public class PasswordUtils {
/** * 匹配密码 * @param salt 盐 * @param rawPass 明文 * @param encPass 密文 * @return */ public static boolean matches(String salt, String rawPass, String encPass) {
return new PasswordEncoder(salt).matches(encPass, rawPass); } /** * 明文密码加密 * @param rawPass 明文 * @param salt * @return */ public static String encode(String rawPass, String salt) {
return new PasswordEncoder(salt).encode(rawPass); } /** * 获取加密盐 * @return */ public static String getSalt() {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 20); }}
  • 密码加密 PasswordEncoder
public class PasswordEncoder {
private final static String[] hexDigits = {
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; private final static String MD5 = "MD5"; private final static String SHA = "SHA"; private Object salt; private String algorithm; public PasswordEncoder(Object salt) {
this(salt, MD5); } public PasswordEncoder(Object salt, String algorithm) {
this.salt = salt; this.algorithm = algorithm; } /** * 密码加密 * @param rawPass * @return */ public String encode(String rawPass) {
String result = null; try {
MessageDigest md = MessageDigest.getInstance(algorithm); // 加密后的字符串 result = byteArrayToHexString(md.digest(mergePasswordAndSalt(rawPass).getBytes("utf-8"))); } catch (Exception ex) {
} return result; } /** * 密码匹配验证 * @param encPass 密文 * @param rawPass 明文 * @return */ public boolean matches(String encPass, String rawPass) {
String pass1 = "" + encPass; String pass2 = encode(rawPass); return pass1.equals(pass2); } private String mergePasswordAndSalt(String password) {
if (password == null) {
password = ""; } if ((salt == null) || "".equals(salt)) {
return password; } else {
return password + "{" + salt.toString() + "}"; } } /** * 转换字节数组为16进制字串 * * @param b * 字节数组 * @return 16进制字串 */ private String byteArrayToHexString(byte[] b) {
StringBuffer resultSb = new StringBuffer(); for (int i = 0; i < b.length; i++) {
resultSb.append(byteToHexString(b[i])); } return resultSb.toString(); } /** * 将字节转换为16进制 * @param b * @return */ private static String byteToHexString(byte b) {
int n = b; if (n < 0) n = 256 + n; int d1 = n / 16; int d2 = n % 16; return hexDigits[d1] + hexDigits[d2]; }
  • Security相关操作
public class SecurityUtils {
/** * 系统登录认证 * @param request * @param username * @param password * @param authenticationManager * @return */ public static JwtAuthenticatioToken login(HttpServletRequest request, String username, String password, AuthenticationManager authenticationManager) {
JwtAuthenticatioToken token = new JwtAuthenticatioToken(username, password); token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); // 执行登录认证过程 Authentication authentication = authenticationManager.authenticate(token); // 认证成功存储认证信息到上下文 SecurityContextHolder.getContext().setAuthentication(authentication); // 生成令牌并返回给客户端 token.setToken(JwtTokenUtils.generateToken(authentication)); return token; } /** * 获取令牌进行认证 * @param request */ public static void checkAuthentication(HttpServletRequest request) {
// 获取令牌并根据令牌获取登录认证信息 Authentication authentication = JwtTokenUtils.getAuthenticationeFromToken(request); // 设置登录认证信息到上下文 SecurityContextHolder.getContext().setAuthentication(authentication); } /** * 获取当前用户名 * @return */ public static String getUsername() {
String username = null; Authentication authentication = getAuthentication(); if(authentication != null) {
Object principal = authentication.getPrincipal(); if(principal != null && principal instanceof UserDetails) {
username = ((UserDetails) principal).getUsername(); } } return username; } /** * 获取用户名 * @return */ public static String getUsername(Authentication authentication) {
String username = null; if(authentication != null) {
Object principal = authentication.getPrincipal(); if(principal != null && principal instanceof UserDetails) {
username = ((UserDetails) principal).getUsername(); } } return username; } /** * 获取当前登录信息 * @return */ public static Authentication getAuthentication() {
if(SecurityContextHolder.getContext() == null) {
return null; } Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); return authentication; } }
  • JWT工具类
public class JwtTokenUtils implements Serializable {
private static final long serialVersionUID = 1L; /** * 用户名称 */ private static final String USERNAME = Claims.SUBJECT; /** * 创建时间 */ private static final String CREATED = "created"; /** * 权限列表 */ private static final String AUTHORITIES = "authorities"; /** * 密钥 */ private static final String SECRET = "abcdefgh"; /** * 有效期12小时 */ private static final long EXPIRE_TIME = 12 * 60 * 60 * 1000; /** * 生成令牌 * * @return 令牌 */ public static String generateToken(Authentication authentication) {
Map
claims = new HashMap<>(3); claims.put(USERNAME, SecurityUtils.getUsername(authentication)); claims.put(CREATED, new Date()); claims.put(AUTHORITIES, authentication.getAuthorities()); return generateToken(claims); } /** * 从数据声明生成令牌 * * @param claims 数据声明 * @return 令牌 */ private static String generateToken(Map
claims) {
Date expirationDate = new Date(System.currentTimeMillis() + EXPIRE_TIME); return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, SECRET).compact(); } /** * 从令牌中获取用户名 * * @param token 令牌 * @return 用户名 */ public static String getUsernameFromToken(String token) {
String username; try {
Claims claims = getClaimsFromToken(token); username = claims.getSubject(); } catch (Exception e) {
username = null; } return username; } /** * 根据请求令牌获取登录认证信息 * @return 用户名 */ public static Authentication getAuthenticationeFromToken(HttpServletRequest request) {
Authentication authentication = null; // 获取请求携带的令牌 String token = JwtTokenUtils.getToken(request); if(token != null) {
// 请求令牌不能为空 if(SecurityUtils.getAuthentication() == null) {
// 上下文中Authentication为空 Claims claims = getClaimsFromToken(token); if(claims == null) {
return null; } String username = claims.getSubject(); if(username == null) {
return null; } if(isTokenExpired(token)) {
return null; } Object authors = claims.get(AUTHORITIES); List
authorities = new ArrayList
(); if (authors != null && authors instanceof List) {
for (Object object : (List) authors) {
authorities.add(new GrantedAuthorityImpl((String) ((Map) object).get("authority"))); } } authentication = new JwtAuthenticatioToken(username, null, authorities, token); } else {
if(validateToken(token, SecurityUtils.getUsername())) {
// 如果上下文中Authentication非空,且请求令牌合法,直接返回当前登录认证信息 authentication = SecurityUtils.getAuthentication(); } } } return authentication; } /** * 从令牌中获取数据声明 * * @param token 令牌 * @return 数据声明 */ private static Claims getClaimsFromToken(String token) {
Claims claims; try {
claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody(); } catch (Exception e) {
claims = null; } return claims; } /** * 验证令牌 * @param token * @param username * @return */ public static Boolean validateToken(String token, String username) {
String userName = getUsernameFromToken(token); return (userName.equals(username) && !isTokenExpired(token)); } /** * 刷新令牌 * @param token * @return */ public static String refreshToken(String token) {
String refreshedToken; try {
Claims claims = getClaimsFromToken(token); claims.put(CREATED, new Date()); refreshedToken = generateToken(claims); } catch (Exception e) {
refreshedToken = null; } return refreshedToken; } /** * 判断令牌是否过期 * * @param token 令牌 * @return 是否过期 */ public static Boolean isTokenExpired(String token) {
try {
Claims claims = getClaimsFromToken(token); Date expiration = claims.getExpiration(); return expiration.before(new Date()); } catch (Exception e) {
return false; } } /** * 获取请求token * @param request * @return */ public static String getToken(HttpServletRequest request) {
String token = request.getHeader("Authorization"); String tokenHead = "Bearer "; if(token == null) {
token = request.getHeader("token"); } else if(token.contains(tokenHead)){
token = token.substring(tokenHead.length()); } if("".equals(token)) {
token = null; } return token; }}

6.测试

在这里插入图片描述

在这里插入图片描述
后端打印的内容:
在这里插入图片描述

转载地址:http://ttten.baihongyu.com/

你可能感兴趣的文章
ovs-brcompatd is not running的解决办法
查看>>
zipimport.ZipImportError: bad local file header错误的解决办法
查看>>
Ubuntu下卸载mysql
查看>>
Intel 100芯片组如何安装Win7
查看>>
Ubuntu 16.04 LTS 一键安装VNC
查看>>
Linux中su命令与sudo命令
查看>>
题目1:二维数组中的查找
查看>>
anaconda conda 切换镜像源
查看>>
Python之面向对象
查看>>
Django项目允许外部通过ip访问
查看>>
Numpy之调整数组大小
查看>>
numpy求解方程组
查看>>
免费人文数据分享网站(更新中)
查看>>
GP工具设置处理范围
查看>>
mapbox根据多边形选择点要素
查看>>
Numpy为图片四周补0
查看>>
数字图像处理中的 channels_first与channels_last
查看>>
ArcGIS 10.2 简化面/线工具Bug修复
查看>>
GPU
查看>>
Android Audio Feature
查看>>