增加登录验证码

This commit is contained in:
wangiegie@gmail.com 2017-12-18 21:32:59 +08:00
parent eae03d88f6
commit 4422453545
17 changed files with 432 additions and 43 deletions

View File

@ -0,0 +1,70 @@
package com.github.pig.admin.common.util;
import com.github.pig.common.vo.ImageCode;
import org.springframework.stereotype.Component;
import com.github.pig.common.constant.SecurityConstants;
import org.springframework.web.context.request.ServletWebRequest;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
/**
* @author lengleng
* @date 2017-12-18
*/
public class ImageCodeGenerator {
public ImageCode generate(ServletWebRequest request) {
BufferedImage image = new BufferedImage(SecurityConstants.DEFAULT_IMAGE_WIDTH, SecurityConstants.DEFAULT_IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
Random random = new Random();
g.setColor(getRandColor(200, 250));
g.fillRect(0, 0, SecurityConstants.DEFAULT_IMAGE_WIDTH, SecurityConstants.DEFAULT_IMAGE_HEIGHT);
g.setFont(new Font("Times New Roman", Font.ITALIC, 20));
g.setColor(getRandColor(160, 200));
for (int i = 0; i < 155; i++) {
int x = random.nextInt(SecurityConstants.DEFAULT_IMAGE_WIDTH);
int y = random.nextInt(SecurityConstants.DEFAULT_IMAGE_HEIGHT);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
g.drawLine(x, y, x + xl, y + yl);
}
String sRand = "";
for (int i = 0; i < SecurityConstants.DEFAULT_IMAGE_LENGTH; i++) {
String rand = String.valueOf(random.nextInt(10));
sRand += rand;
g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
g.drawString(rand, 13 * i + 6, 16);
}
g.dispose();
return new ImageCode(image, sRand, SecurityConstants.DEFAULT_IMAGE_EXPIRE);
}
/**
* 生成随机背景条纹
*
* @param fc
* @param bc
* @return
*/
private Color getRandColor(int fc, int bc) {
Random random = new Random();
if (fc > 255) {
fc = 255;
}
if (bc > 255) {
bc = 255;
}
int r = fc + random.nextInt(bc - fc);
int g = fc + random.nextInt(bc - fc);
int b = fc + random.nextInt(bc - fc);
return new Color(r, g, b);
}
}

View File

@ -0,0 +1,41 @@
package com.github.pig.admin.controller;
import com.github.pig.admin.common.util.ImageCodeGenerator;
import com.github.pig.admin.service.UserService;
import com.github.pig.common.constant.SecurityConstants;
import com.github.pig.common.vo.ImageCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.context.request.ServletWebRequest;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author lengleng
* @date 2017/12/18
*/
@Controller
public class ImageCodeController {
@Autowired
private UserService userService;
/**
* 创建验证码
*
* @param request request
* @throws Exception
*/
@GetMapping(SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX + "/{randomStr}")
public void createCode(@PathVariable String randomStr, HttpServletRequest request, HttpServletResponse response)
throws Exception {
response.setHeader("Cache-Control", "no-store, no-cache");
response.setContentType("image/jpeg");
ImageCode imageCode = new ImageCodeGenerator().generate(new ServletWebRequest(request));
userService.save(randomStr, imageCode);
ImageIO.write(imageCode.getImage(), "JPEG",response.getOutputStream());
}
}

View File

@ -2,17 +2,20 @@ package com.github.pig.admin.controller;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.plugins.Page;
import com.github.pig.admin.common.util.ImageCodeGenerator;
import com.github.pig.admin.dto.UserDto;
import com.github.pig.common.bean.QiniuPropertiesConfig;
import com.github.pig.admin.dto.UserInfo;
import com.github.pig.admin.entity.SysUser;
import com.github.pig.admin.entity.SysUserRole;
import com.github.pig.admin.service.SysMenuService;
import com.github.pig.admin.service.SysUserRoleService;
import com.github.pig.admin.service.UserService;
import com.github.pig.common.bean.QiniuPropertiesConfig;
import com.github.pig.common.constant.CommonConstant;
import com.github.pig.common.constant.SecurityConstants;
import com.github.pig.common.util.Query;
import com.github.pig.common.util.R;
import com.github.pig.common.util.UserUtils;
import com.github.pig.common.vo.ImageCode;
import com.github.pig.common.vo.UserVo;
import com.github.pig.common.web.BaseController;
import com.qiniu.common.Zone;
@ -26,9 +29,12 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@ -46,31 +52,17 @@ public class UserController extends BaseController {
@Autowired
private SysUserRoleService sysUserRoleService;
@Autowired
private SysMenuService sysMenuService;
@Autowired
private QiniuPropertiesConfig qiniuPropertiesConfig;
/**
* 获取当前用户的用户名
* 获取当前用户信息角色权限
*
* @return 用户名
*/
@GetMapping("/info")
public UserInfo user() {
SysUser condition = new SysUser();
condition.setUsername(UserUtils.getUserName());
SysUser sysUser = userService.selectOne(new EntityWrapper<>(condition));
UserInfo userInfo = new UserInfo();
userInfo.setSysUser(sysUser);
//设置角色列表
String[] roles = getRole().toArray(new String[getRole().size()]);
userInfo.setRoles(roles);
//设置权限列表menu.permission
String[] permissions = sysMenuService.findPermission(roles);
userInfo.setPermissions(permissions);
return userInfo;
public R<UserInfo> user() {
return new R<>(userService.findUserInfo(getRole()));
}
/**
@ -98,10 +90,10 @@ public class UserController extends BaseController {
if (delUserInfo) {
userService.clearCache(UserUtils.getUserName());
return Boolean.TRUE;
}else {
} else {
return Boolean.FALSE;
}
}else {
} else {
return Boolean.FALSE;
}
}
@ -160,9 +152,7 @@ public class UserController extends BaseController {
/**
* 分页查询用户
*
* @param page 页码
* @param limit 每页数量
* @param sysUser 检索条件
* @param params 参数集
* @return 用户集合
*/
@RequestMapping("/userPage")

View File

@ -2,9 +2,14 @@ package com.github.pig.admin.service;
import com.baomidou.mybatisplus.plugins.Page;
import com.baomidou.mybatisplus.service.IService;
import com.github.pig.admin.dto.UserInfo;
import com.github.pig.admin.entity.SysUser;
import com.github.pig.common.util.Query;
import com.github.pig.common.vo.ImageCode;
import com.github.pig.common.vo.UserVo;
import org.springframework.web.context.request.ServletWebRequest;
import java.util.List;
/**
* @author lengleng
@ -33,4 +38,20 @@ public interface UserService extends IService<SysUser> {
* @param userName 用户名
*/
void clearCache(String userName);
/**
* 查询用户信息
*
* @param roleNames 角色名
* @return userInfo
*/
UserInfo findUserInfo(List<String> roleNames);
/**
* 保存验证码
*
* @param randomStr 随机串
* @param imageCode 验证码
*/
void save(String randomStr, ImageCode imageCode);
}

View File

@ -0,0 +1,19 @@
package com.github.pig.admin.service.impl;
import com.github.pig.common.constant.SecurityConstants;
import com.github.pig.common.vo.ImageCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.ServletWebRequest;
import javax.imageio.ImageIO;
/**
* @author lengleng
* @date 2017/12/18
*/
@Component("imageCodeService")
public class ImageCodeServiceImpl {
}

View File

@ -1,16 +1,28 @@
package com.github.pig.admin.service.impl;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.plugins.Page;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.github.pig.admin.dto.UserInfo;
import com.github.pig.admin.entity.SysUser;
import com.github.pig.admin.mapper.SysUserMapper;
import com.github.pig.admin.service.SysMenuService;
import com.github.pig.admin.service.UserService;
import com.github.pig.common.constant.SecurityConstants;
import com.github.pig.common.util.Query;
import com.github.pig.common.util.UserUtils;
import com.github.pig.common.vo.ImageCode;
import com.github.pig.common.vo.UserVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.ServletWebRequest;
import javax.imageio.ImageIO;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* @author lengleng
@ -18,9 +30,30 @@ import org.springframework.stereotype.Service;
*/
@Service
public class UserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements UserService {
@Autowired
private SysMenuService sysMenuService;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private SysUserMapper sysUserMapper;
@Override
public UserInfo findUserInfo(List<String> roleNames) {
SysUser condition = new SysUser();
condition.setUsername(UserUtils.getUserName());
SysUser sysUser = this.selectOne(new EntityWrapper<>(condition));
UserInfo userInfo = new UserInfo();
userInfo.setSysUser(sysUser);
//设置角色列表
String[] roles = roleNames.toArray(new String[roleNames.size()]);
userInfo.setRoles(roles);
//设置权限列表menu.permission
String[] permissions = sysMenuService.findPermission(roles);
userInfo.setPermissions(permissions);
return userInfo;
}
@Override
@Cacheable(value = "user_details", key = "#username")
public UserVo findUserByUsername(String username) {
@ -38,4 +71,15 @@ public class UserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impleme
public void clearCache(String username) {
}
/**
* 保存用户验证码和randomStr绑定
*
* @param randomStr 客户端生成
* @param imageCode 验证码信息
*/
@Override
public void save(String randomStr, ImageCode imageCode) {
redisTemplate.opsForValue().set(SecurityConstants.DEFAULT_CODE_KEY + randomStr,imageCode.getCode(),SecurityConstants.DEFAULT_IMAGE_EXPIRE, TimeUnit.SECONDS);
}
}

View File

@ -1,5 +1,6 @@
package com.github.pig.auth.controller;
import com.github.pig.common.util.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
@ -31,11 +32,11 @@ public class UserController {
* @return true/false
*/
@PostMapping("/removeToken")
public Boolean removeToken(String accesstoken, String refreshToken) {
public R<Boolean> removeToken(String accesstoken, String refreshToken) {
RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
tokenStore.removeRefreshToken(refreshToken);
tokenStore.removeAccessToken(accesstoken);
return Boolean.TRUE;
return new R<>(Boolean.TRUE);
}
}

View File

@ -1,7 +1,6 @@
package com.github.pig.common.bean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
/**
@ -10,15 +9,11 @@ import org.springframework.context.annotation.Configuration;
* 七牛参数
*/
@Configuration
@ConfigurationProperties(prefix = "qiniu")
@ConditionalOnProperty(prefix = "qiniu", name = "accessKey")
public class QiniuPropertiesConfig {
@Value("${qiniu.accessKey}")
private String accessKey;
@Value("${qiniu.secretKey}")
private String secretKey;
@Value("${qiniu.bucket}")
private String bucket;
@Value("${qiniu.host}")
private String qiniuHost;
public String getAccessKey() {

View File

@ -46,4 +46,11 @@ public interface CommonConstant {
* 删除标记
*/
String DEL_FLAG = "del_flag";
/**
* 编码
*/
String UTF8 = "UTF-8";
String CONTENT_TYPE = "application/json; charset=utf-8";
}

View File

@ -0,0 +1,39 @@
package com.github.pig.common.constant;
/**
* @author lengleng
* @date 2017-12-18
*/
public interface SecurityConstants {
/**
* 默认的处理验证码的url前缀
*/
String DEFAULT_VALIDATE_CODE_URL_PREFIX = "/code";
/**
* 默认生成图形验证码宽度
*/
int DEFAULT_IMAGE_WIDTH = 67;
/**
* 默认生成图像验证码高度
*/
int DEFAULT_IMAGE_HEIGHT = 23;
/**
* 默认生成图形验证码长度
*/
int DEFAULT_IMAGE_LENGTH = 4;
/**
* 默认生成图形验证码过期时间
*/
int DEFAULT_IMAGE_EXPIRE = 60;
/**
* 默认保存code的前缀
*/
String DEFAULT_CODE_KEY = "DEFAULT_CODE_KEY";
}

View File

@ -2,7 +2,11 @@ package com.github.pig.common.util;
import java.io.Serializable;
/**
* 响应信息主体
*
* @param <T>
*/
public class R<T> implements Serializable {
private static final long serialVersionUID = 1L;

View File

@ -15,7 +15,6 @@ import java.util.Base64;
* @author lengleng
* @date 2017/11/20
* 用户相关工具类
* TODO theardLocal 避免上下文开销
*/
public class UserUtils {
private static Logger logger = LoggerFactory.getLogger(UserUtils.class);

View File

@ -0,0 +1,14 @@
package com.github.pig.common.util.exception;
public class ValidateCodeException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = -7285211528095468156L;
public ValidateCodeException(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,52 @@
package com.github.pig.common.vo;
import java.awt.image.BufferedImage;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @author lengleng
* @date 2017-12-18
*/
public class ImageCode implements Serializable {
private String code;
private LocalDateTime expireTime;
private BufferedImage image;
public ImageCode(BufferedImage image, String sRand, int defaultImageExpire) {
this.image = image;
this.code = sRand;
this.expireTime = LocalDateTime.now().plusSeconds(defaultImageExpire);
}
public boolean isExpried() {
return LocalDateTime.now().isAfter(expireTime);
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public LocalDateTime getExpireTime() {
return expireTime;
}
public void setExpireTime(LocalDateTime expireTime) {
this.expireTime = expireTime;
}
public BufferedImage getImage() {
return image;
}
public void setImage(BufferedImage image) {
this.image = image;
}
}

View File

@ -5,6 +5,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
/**
@ -15,6 +16,7 @@ import org.springframework.security.config.annotation.method.configuration.Enabl
@SpringBootApplication
@EnableFeignClients
@EnableGlobalMethodSecurity(prePostEnabled = true)
@ComponentScan(basePackages = {"com.github.pig.gateway", "com.github.pig.common.bean"})
public class PigGatewayApplication {
public static void main(String[] args) {

View File

@ -1,7 +1,7 @@
package com.github.pig.gateway.config;
import com.github.pig.gateway.filter.ValidateCodeFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -11,9 +11,7 @@ import org.springframework.security.oauth2.config.annotation.web.configuration.E
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.expression.OAuth2WebSecurityExpressionHandler;
import java.util.List;
import java.util.Set;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @author lengleng
@ -26,9 +24,12 @@ public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter
private FilterUrlsPropertiesConifg filterUrlsPropertiesConifg;
@Autowired
private OAuth2WebSecurityExpressionHandler expressionHandler;
@Autowired
private ValidateCodeFilter validateCodeFilter;
@Override
public void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http
.authorizeRequests();
for (String url : filterUrlsPropertiesConifg.getAnon()) {
@ -39,11 +40,11 @@ public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.expressionHandler(expressionHandler);
}
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.expressionHandler(expressionHandler);
}
/**
/**
* 配置解决 spring-security-oauth问题
* https://github.com/spring-projects/spring-security-oauth/issues/730
*

View File

@ -0,0 +1,90 @@
package com.github.pig.gateway.filter;
import com.alibaba.fastjson.JSONObject;
import com.github.pig.common.constant.CommonConstant;
import com.github.pig.common.constant.SecurityConstants;
import com.github.pig.common.util.R;
import com.github.pig.common.util.exception.ValidateCodeException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author lengleng
* @date 2017-12-18
* 验证码校验
*/
@Component("validateCodeFilter")
public class ValidateCodeFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(ValidateCodeFilter.class);
@Value("${security.login.url}")
private String loginUrl;
@Autowired
private RedisTemplate redisTemplate;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (StringUtils.contains(request.getRequestURI(), loginUrl)) {
PrintWriter printWriter = null;
try {
checkCode(request, response, filterChain);
} catch (ValidateCodeException e) {
response.setCharacterEncoding(CommonConstant.UTF8);
response.setContentType(CommonConstant.CONTENT_TYPE);
R<String> result = new R<>(e.getMessage());
result.setCode(478);
response.setStatus(478);
printWriter = response.getWriter();
printWriter.append(JSONObject.toJSONString(result));
} finally {
IOUtils.closeQuietly(printWriter);
}
} else {
filterChain.doFilter(request, response);
}
}
private void checkCode(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws IOException, ServletException {
String code = httpServletRequest.getParameter("code");
String randomStr = httpServletRequest.getParameter("randomStr");
Object codeObj = redisTemplate.opsForValue().get(SecurityConstants.DEFAULT_CODE_KEY + randomStr);
if (codeObj == null) {
throw new ValidateCodeException("验证码已过期或已过期");
}
String saveCode = codeObj.toString();
if (StringUtils.isBlank(code)) {
redisTemplate.delete(SecurityConstants.DEFAULT_CODE_KEY + randomStr);
throw new ValidateCodeException("验证码的值不能为空");
}
if (StringUtils.isEmpty(saveCode)) {
redisTemplate.delete(SecurityConstants.DEFAULT_CODE_KEY + randomStr);
throw new ValidateCodeException("验证码已过期或已过期");
}
if (!StringUtils.equals(saveCode, code)) {
redisTemplate.delete(SecurityConstants.DEFAULT_CODE_KEY + randomStr);
throw new ValidateCodeException("验证码不匹配");
}
if (StringUtils.equals(code, saveCode)) {
redisTemplate.delete(SecurityConstants.DEFAULT_CODE_KEY + randomStr);
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
}