1 开发基于表单的认证

Spring security核心的功能

  • 认证(你是谁?)
  • 授权(你能干什么?)
  • 攻击防护(防止伪造身份)

spring security实现了默认的用户名+密码认证,默认用户名为user,密码为:

spring security基本原理:过滤器链

  对于UsernamePasswordAuthenticationFilter只会拦截 url为/login,method为POST的请求。

1.1 自定义用户认证逻辑

1)处理用户信息获取逻辑

  UserDetailsService接口,只有一个方法:loadUserByUsername

实现该接口:数据库中存放的是加密密码,对于同一个密码不同时间的加密密文不一样

@Component
public class MyUserDetailsService implements UserDetailsService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
logger.info("用户名信息:" + s);
// 根据用户名查找用户信息
logger.info("数据库密码:" + passwordEncoder.encode("123456"));
// 用户名和密码信息用来做认证,权限信息用来对该用户做授权
return new User(s, "$2a$10$eFw06n0ABK2NFuse8y5f/eDUq7we26qQTceEtXSWNbMXnQ5Yf5Iha",
AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}

2)处理用户信息校验逻辑

  处理密码加密解密:在配置文件中将PasswordEncoder对象注入spring容器,等价于@Component+包扫描组件

1.2 个性化用户认证流程

1)对于浏览器,返回自定义登录页面,让UsernamePasswordXxxFilter来处理登录请求;对于调用RESTful服务,返回json错误信息。

   用户登录成功后 ,对于浏览器,返回需要的页面;对于服务,返回json数据。

  权限配置:

    @Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()// 表单登录
.loginPage("/authentication/require") //将登录页面重定向到controller
.loginProcessingUrl("/authentication/form")
.and()
.authorizeRequests() //请求授权
.antMatchers("/authentication/require",
securityProperties.getBrowser().getLoginPage()).permitAll()//该页面允许通过
.anyRequest()
.authenticated() // 其他资源需要认证
.and()
.csrf().disable(); // 将跨站防护关掉
}

  

  控制器,根据之前URL的路径判断是否为RESTful服务,在处理

/*
当客户端发出请求,当需要认证时,spring security会重定向到该控制器
*/
@RestController
public class BrowserSecurityController {
private Logger logger = LoggerFactory.getLogger(getClass()); // 请求缓存
private RequestCache requestCache = new HttpSessionRequestCache();
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Autowired
private SecurityProperties securityProperties;
/**
* 当需要身份认证时跳转到这里
* @param request
* @param response
* @return
*/
@RequestMapping("/authentication/require")
@ResponseStatus(code = HttpStatus.UNAUTHORIZED)
public SimpleResponse requireAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 判断请求类型,HTML或者app
SavedRequest savedRequest = requestCache.getRequest(request, response);
if(savedRequest!=null){
String targetUrl = savedRequest.getRedirectUrl();
logger.info("引发跳转的URL:"+targetUrl);
// 如果之前的URL为.html结尾的URL,则重定向到登录页面
if(StringUtils.endsWithIgnoreCase(targetUrl, ".html")){
redirectStrategy.sendRedirect(request, response,
securityProperties.getBrowser().getLoginPage());
}
}
return new SimpleResponse("请求的服务需要身份认证,请引导用户到登录页面");
}
}

BrowserSecurityController.java

  在启动项目中的application.properties文件中配置登录页面:

# 配置登录页面
getword.security.browser.loginPage=/demo.html

  读取配置文件信息:

  

import org.springframework.boot.context.properties.ConfigurationProperties;

// 读取前缀为getword.security的属性配置,其中browser中的属性会被读取到browserProperties中
@ConfigurationProperties(prefix = "getword.security")
public class SecurityProperties {
// browser的属性会匹配getword.security.browser后面的属性
private BrowserProperties browser = new BrowserProperties(); public BrowserProperties getBrowser() {
return browser;
} public void setBrowser(BrowserProperties browser) {
this.browser = browser;
}
}

SecurityProperties.java

public class BrowserProperties {
private String loginPage = "/login.html"; //默认值 public String getLoginPage() {
return loginPage;
} public void setLoginPage(String loginPage) {
this.loginPage = loginPage;
}
}

BrowserProperties.java

@Configuration
@EnableConfigurationProperties(SecurityProperties.class) //让属性配置读取器生效
public class SecurityCodeConfig {
}

2)自定义登录成功处理,异步登录,AuthenticationSuccessHandler接口

  自定义登录成处理:

@Component("vstudyAuthenticationSuccessHandler")
public class VstudyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
//工具类, 将对象转成json
@Autowired
private ObjectMapper objectMapper;
// 登录成功后调用
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
logger.info("登录成功");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
}
}

VstudyAuthenticationSuccessHandler

  注册,使处理器生效:

3)登录失败处理

@Component("vstudyAuthenticationFailHandler")
public class VstudyAuthenticationFailHandler implements AuthenticationFailureHandler {
@Autowired
private ObjectMapper objectMapper; private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException e) throws IOException, ServletException {
logger.info("登录失败");
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); //服务器内部错误
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(objectMapper.writeValueAsString(e));
}
}

VstudyAuthenticationFailHandler.java

配置:和success类似

4)判断请求方式,做出相应的处理

successHandler:

@Component("vstudyAuthenticationSuccessHandler")
public class VstudyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
//工具类, 将对象转成json
@Autowired
private ObjectMapper objectMapper; @Autowired
private SecurityProperties securityProperties; //获取配置信息 // 登录成功后调用
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
logger.info("登录成功");
if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
}else{
// 调用父类方法,完成重定向跳转
super.onAuthenticationSuccess(request, response, authentication);
}
}
}

VstudyAuthenticationSuccessHandler

failureHandler:

@Component("vstudyAuthenticationFailHandler")
public class VstudyAuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler {
@Autowired
private SecurityProperties securityProperties;
@Autowired
private ObjectMapper objectMapper; private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException e) throws IOException, ServletException {
logger.info("登录失败");
if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); //服务器内部错误
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(objectMapper.writeValueAsString(e));
}else{
super.onAuthenticationFailure(request, response, e);
}
}
}

VstudyAuthenticationFailHandler

2 认证流程

3 图形验证码

3.1 生成图形验证码

  验证码图片信息:

public class ImageCode {
private BufferedImage image;
private String code;
private LocalDateTime expireTime;//过期时间
public ImageCode(BufferedImage image, String code, int expireIn){
this.image = image;
this.code = code;
this.expireTime = LocalDateTime.now().plusSeconds(expireIn);
} public ImageCode(BufferedImage image, String code, LocalDateTime expireTime){
this.image = image;
this.code = code;
this.expireTime = expireTime;
}
}

ImageCode

  控制器:

@RestController
public class ValidateCodeController {
public static String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); @Autowired
private SecurityProperties securityProperties; @GetMapping("/image/code")
public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
ImageCode imageCode = createImageCode(new ServletWebRequest(request,response));
sessionStrategy.setAttribute(new ServletWebRequest(request, response), SESSION_KEY, imageCode);
ImageIO.write(imageCode.getImage(), "JPEG", response.getOutputStream());
} /**
* 生成ImageCode验证码
* @param request
* @return
*/
public ImageCode createImageCode(ServletWebRequest request){
// 生成验证码,方法很多
int width = 60;
int height = 20;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics g = image.getGraphics(); Random random = new Random(); g.setColor(getRandColor(200, 250));
g.fillRect(0, 0, width, 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(width);
int y = random.nextInt(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 < 4; 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, 100);
}
/**
* 生成随机背景条纹
*
* @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);
}
}

ValidateCodeController

3.2 验证码校验

自定义过滤器:

public class ValidateCodeFilter extends OncePerRequestFilter {
/**
* 验证码校验失败处理器
*/
private AuthenticationFailureHandler authenticationFailureHandler;
/**
* 系统配置信息
*/
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
/**
* 系统中的校验码处理器
*/ @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 只处理登录请求
if (StringUtils.equals("/authetication/form", request.getRequestURI())
&& StringUtils.equalsIgnoreCase(request.getMethod(), "POST")) {
try {
logger.info("验证码校验通过");
} catch (ValidateCodeException e) {
//验证失败
authenticationFailureHandler.onAuthenticationFailure(request, response, e);
}
} filterChain.doFilter(request, response);
}
protected void validate(ServletWebRequest request) throws ServletRequestBindingException {
// 从session中拿到imageCode
ImageCode codeInSession = (ImageCode) sessionStrategy.getAttribute(request, ValidateCodeController.SESSION_KEY);
// 获取客户端输入的code,当前请求参数
String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "imageCode");
if(StringUtils.isBlank(codeInRequest)){
throw new ValidateCodeException("验证码不能为空");
}
if(codeInSession==null){
throw new ValidateCodeException("验证码不存在");
}
if(codeInSession.isExpired()){
throw new ValidateCodeException("验证码已过期");
}
if(!StringUtils.equalsIgnoreCase(codeInSession.getCode(), codeInRequest)){
throw new ValidateCodeException("验证码不匹配");
}
sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
} public AuthenticationFailureHandler getAuthenticationFailureHandler() {
return authenticationFailureHandler;
} public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
this.authenticationFailureHandler = authenticationFailureHandler;
}
}

ValidateCodeFilter

配置:

执行流程:

/index.html ->redirect ->/authentication/require(控制器,判断是.html结尾)->login.html ->ValidateCodeFilter ->exception -> VstudyAuthenticationFailHandler ->loginType:JSON

login.html中,使用ajax发送登录请求 -> 验证码过滤器通过 -> UsernamePasswordFilter通过 -> 返回登录结果信息

3.3 验证码的接口

  • 为了方便修改验证码的参数,如宽度、高度、长度等信息,我们将通过配置文件的形式配置这些信息。还有验证码的URL地址。
  • 验证码拦截的接口可配置,比如为了限制用户操作频率,对用户操作使用验证码进行限制。验证码过滤器可以拦截多个控制器请求。
  • 验证码的生成逻辑可以配置

三级配置:

 1)验证码参数:

  默认配置:

/**
* 图形验证码
*/
public class ImageCodeProperties {
private int width = 67;
private int height = 23;
private int len = 4;
private int expireIn = 60; //60秒后过期
//seter getter
}

验证码信息:

/**
* 验证码:包括图形验证码、短信验证码
*/
public class ValidateCodeProperties {
private ImageCodeProperties image;
}

属性读取:

@ConfigurationProperties(prefix = "getword.security")
public class SecurityProperties {
// browser的属性会匹配getword.security.browser后面的属性
private BrowserProperties browser = new BrowserProperties(); // 验证码属性匹配getword.security.code后面的属性
private ValidateCodeProperties code = new ValidateCodeProperties();
}

end

最新文章

  1. 【腾讯Bugly干货分享】聊聊苹果的Bug - iOS 10 nano_free Crash
  2. C++的性能C#的产能?! - .Net Native 系列向导
  3. MySQL 5.5安装记录
  4. core文件
  5. ie-css3.htc 可以让IE低版本浏览器支持CSS3 的一个小工具
  6. java基础比较好的笔记总结
  7. JBoss的安装与配置(对应eclipse配置)【转】
  8. hdu 2044
  9. 所学新知——int、char型转string 类型等
  10. Java 线程池学习
  11. dedecms5.7文章实现阅读全文功能二次开发
  12. GNU FORK PTHREAD SIGNALS
  13. 关于栈和堆的定量分析(★firecat推荐★)
  14. Android自定义ListView的Item无法响应OnItemClick的解决办法(转)
  15. Thinkphp3.2简单解决多文件上传只上传一张的问题
  16. Day15 Javascipt内容补充
  17. okhttp 内网可以有,但外网访问数据返不回来,代码一样
  18. java基础(个人学习笔记) A
  19. [UE4]条件融合动画: Blend Posed by int
  20. win7下解决烦人的管理员权限问题

热门文章

  1. docker为什么适合devops?
  2. centos绑定https
  3. rpm 卸载
  4. [真题] 一道 vsftp 运维题
  5. 部署LVS-NAT群集
  6. navicat for oracle 创建表ID字段的自动递增
  7. vue+ivew-admin开发项目,内存占用过大解决办法
  8. HTML02--引用样式、表格、列表、div布局
  9. BERT和ULMFIT embedding比较文本分类结果
  10. python连接,操作 InfluxDB