添加该功能是在原有功能上新增功能: SpringBoot +SpringSecurity+mysql 实现用户数据权限管理
本文仅做重点代码的和相关依赖说明:SpringBoot +SpringSecurity+mysql 实现用户数据权限管理 文章中,我们采用的了分布式架构搭建该项目,同时也期望将相关通过公共组件抽离进行封装。因此。我们在common-tool 模块中添加kaptcha jar 文件依赖。
pom.xml 文件如下:
com.github.penggle
kaptcha
2.3.2
我们这里采用编码方式,实例化kaptcha的配置对象:com.google.code.kaptcha.impl.DefaultKaptcha
核心代码:
package com.zzg.kaptcha.single;
import java.util.Properties;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
public class KaptchaSingle {
private static KaptchaSingle instance;
private KaptchaSingle() {
};
public static KaptchaSingle getInstance() {
if (instance == null) {
instance = new KaptchaSingle();
}
return instance;
}
/**
* 生成DefaultKaptcha 默认配置
* @return
*/
public DefaultKaptcha produce() {
Properties properties = new Properties();
properties.put("kaptcha.border", "no");
properties.put("kaptcha.border.color", "105,179,90");
properties.put("kaptcha.textproducer.font.color", "blue");
properties.put("kaptcha.image.width", "100");
properties.put("kaptcha.image.height", "50");
properties.put("kaptcha.textproducer.font.size", "27");
properties.put("kaptcha.session.key", "code");
properties.put("kaptcha.textproducer.char.length", "4");
properties.put("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
properties.put("kaptcha.textproducer.char.string", "0123456789ABCEFGHIJKLMNOPQRSTUVWXYZ");
properties.put("kaptcha.obscurificator.impl", "com.google.code.kaptcha.impl.WaterRipple");
properties.put("kaptcha.noise.color", "black");
properties.put("kaptcha.noise.impl", "com.google.code.kaptcha.impl.DefaultNoise");
properties.put("kaptcha.background.clear.from", "185,56,213");
properties.put("kaptcha.background.clear.to", "white");
properties.put("kaptcha.textproducer.char.space", "3");
Config config = new Config(properties);
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
注意:上述 核心代码还有可以优化的地方,主要集中于Properties 类 添加配置参数的方式,建议采用读取classpath 文件夹下的相关资源文件。
业务模块依赖通用组件模块:
1、生成验证码控制层:
package com.zzg.controller;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.zzg.kaptcha.single.KaptchaSingle;
@Controller
public class KaptchaController {
@RequestMapping("/defaultKaptcha")
public void defaultKaptcha(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
throws Exception {
byte[] captchaChallengeAsJpeg = null;
ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
try {
// 代码方式创建:DefaultKaptcha
KaptchaSingle single = KaptchaSingle.getInstance();
DefaultKaptcha defaultKaptcha = single.produce();
// 生产验证码字符串并保存到session中
String createText = defaultKaptcha.createText();
httpServletRequest.getSession().setAttribute("vrifyCode", createText);
// 使用生产的验证码字符串返回一个BufferedImage对象并转为byte写入到byte数组中
BufferedImage challenge = defaultKaptcha.createImage(createText);
ImageIO.write(challenge, "jpg", jpegOutputStream);
} catch (IllegalArgumentException e) {
httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
// 定义response输出类型为image/jpeg类型,使用response输出流输出图片的byte数组
captchaChallengeAsJpeg = jpegOutputStream.toByteArray();
httpServletResponse.setHeader("Cache-Control", "no-store");
httpServletResponse.setHeader("Pragma", "no-cache");
httpServletResponse.setDateHeader("Expires", 0);
httpServletResponse.setContentType("image/jpeg");
ServletOutputStream responseOutputStream = httpServletResponse.getOutputStream();
responseOutputStream.write(captchaChallengeAsJpeg);
responseOutputStream.flush();
responseOutputStream.close();
}
}
2、编辑验证码拦截器:
package com.zzg.security.kaptch.filter;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
/**
* Kaptcha 拦截器
*
* @author zzg
*
*/
public class KaptchaFilter extends AbstractAuthenticationProcessingFilter {
// SESSION 关于 验证码
private static final String VRIFYCODE ="vrifyCode";
// 拦截请求地址
private String servletPath;
public KaptchaFilter(String servletPath, String failureUrl) {
super(servletPath);
this.servletPath = servletPath;
setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(failureUrl));
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// TODO Auto-generated method stub
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
if ("POST".equalsIgnoreCase(req.getMethod()) && servletPath.equals(req.getServletPath())) {
String expect = (String) req.getSession().getAttribute(VRIFYCODE);
if (expect != null && !expect.equalsIgnoreCase(req.getParameter(VRIFYCODE))) {
unsuccessfulAuthentication(req, res, new InsufficientAuthenticationException("输入的验证码不正确"));
return;
}
}
chain.doFilter(req, res);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
// TODO Auto-generated method stub
return null;
}
}
3、spring-security 配置文件,添加验证码拦截器,在验证用户密码拦截器之前:
package com.zzg.security.config;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
import com.zzg.security.kaptch.filter.KaptchaFilter;
import com.zzg.security.provider.SpringSecurityProvider;
/**
* spring-security 配置文件
* @author zzg
*
*/
@Configuration
@EnableWebSecurity //开启Spring Security的功能
@EnableGlobalMethodSecurity(prePostEnabled=true)//开启注解控制权限
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* עSpringSecurityProvider
*/
@Autowired
private SpringSecurityProvider provider;
/**
*AuthenticationSuccessHandler
*/
@Autowired
private AuthenticationSuccessHandler securityAuthenticationSuccessHandler;
/**
* AuthenticationFailureHandler
*/
@Autowired
private AuthenticationFailureHandler securityAuthenticationFailHandler;
@Autowired
private DataSource dataSource;
/**
* 定义需要过滤的静态资源(等价于HttpSecurity的permitAll)
*/
@Override
public void configure(WebSecurity webSecurity) throws Exception {
webSecurity.ignoring().antMatchers("static/css/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// TODO Auto-generated method stub
http.authorizeRequests()
.antMatchers("/login")
.permitAll() // 登入界面不需要权限
.antMatchers("/defaultKaptcha")
.permitAll() // 图像验证码不需要权限
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login") // 登入页面
.successHandler(securityAuthenticationSuccessHandler) //自定义成功处理器
.failureHandler(securityAuthenticationFailHandler) //自定义失败处理器
.permitAll()
.and()
.logout();
// 当通过JDBC方式记住密码时必须设置 key,key 可以为任意非空(null 或 "")字符串,但必须和 RememberMeService 构造参数的
// key 一致,否则会导致通过记住密码登录失败
http.authorizeRequests()
.and()
.rememberMe()
.rememberMeServices(rememberMeServices())
.key("INTERNAL_SECRET_KEY");
//在认证用户名之前认证验证码,如果验证码错误,将不执行用户名和密码的认证
http.addFilterBefore(new KaptchaFilter("/login", "/login?error"), UsernamePasswordAuthenticationFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
// 自定义身份验证提供者
builder.authenticationProvider(provider);
}
/**
* 返回 RememberMeServices 实例
*
* @return the remember me services
*/
@Bean
public RememberMeServices rememberMeServices() {
JdbcTokenRepositoryImpl rememberMeTokenRepository = new JdbcTokenRepositoryImpl();
// 此处需要设置数据源,否则无法从数据库查询验证信息
rememberMeTokenRepository.setDataSource(dataSource);
// 启动创建表,创建成功后注释掉
// rememberMeTokenRepository.setCreateTableOnStartup(true);
// 此处的 key 可以为任意非空值(null 或 ""),单必须和起前面
// rememberMeServices(RememberMeServices rememberMeServices).key(key)的值相同
PersistentTokenBasedRememberMeServices rememberMeServices =
new PersistentTokenBasedRememberMeServices("INTERNAL_SECRET_KEY", provider.getUserDetailsService(), rememberMeTokenRepository);
// 该参数不是必须的,默认值为 "remember-me", 但如果设置必须和页面复选框的 name 一致
rememberMeServices.setParameter("remember-me");
return rememberMeServices;
}
}
用户登入页面(login.html),添加验证码页面:
登录
已成功注销
有错误,请重试
使用用户名密码登录
至此,spring-security 模块添加验证码功能完毕。