Spring Boot + Spring Security 实现验证 未管理角色
首先写一个验证码 pom.xml 引入 kaptcha
<!-- 验证码-->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
生成验证码此接口之后会在security配置文件中暴露出来
package com.skypegmwcn.zhxy.skypegmwcn.controller;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.skypegmwcn.zhxy.skypegmwcn.entity.Login;
import com.skypegmwcn.zhxy.skypegmwcn.service.LoginService;
import com.skypegmwcn.zhxy.skypegmwcn.util.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
@Controller
@AllArgsConstructor
@RequestMapping("/login" )
@Api(value = "login", tags = "用户登录")
public class LoginController {
@Autowired
DefaultKaptcha defaultKaptcha;
//获取验证码
@ApiOperation(value = "查询验证码", notes = "查询验证码")
@GetMapping("/defaultKaptcha")
public void defaultKaptcha(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception{
byte[] captchaChallengeAsJpeg = null;
ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
try {
//生产验证码字符串并保存到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();
}
}
pom.xml引入 security
<!-- 权限登录-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
</dependency>
当pom引入security之后再次访问swagge-ui或接口时已经会显示security默认的登录页 账号默认是user密码会输出在idea控制台中,然后就需要重新写WebSecurityConfigurerAdapter 接口来按照自己需求进行编写
package com.skypegmwcn.zhxy.skypegmwcn.config;
import com.skypegmwcn.zhxy.skypegmwcn.service.impl.CustomUserServicelmpl;
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.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* Created by JiXiang on 19/11/20.
*/
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
CustomUserServicelmpl customUserServicelmpl;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserServicelmpl); //user Details Service验证
}
@Autowired
private ValidateCodeFilter validateCodeFilter; //验证码
@Autowired
private MyAuthenticationSucessHandler myAuthenticationSucessHandler; //正确返回值
@Autowired
private MyAuthenticationFailHandler myAuthenticationFailHandler; //错误返回值
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class) // 添加验证码校验过滤器
.formLogin()
.loginPage("/login/index") //登录页
.loginProcessingUrl("/user/login") //放行登录接口
.successHandler (myAuthenticationSucessHandler)
.failureHandler(myAuthenticationFailHandler)
// .defaultSuccessUrl("/")
// .failureUrl("/login?error")
.permitAll() //登录页面用户任意访问
.and()
.authorizeRequests()
.antMatchers("/login.html","/login/defaultKaptcha").permitAll()//放行登录页与验证码
.anyRequest().authenticated() //任何请求,登录后可以访问
.and()
.logout().permitAll().and().csrf().disable();//注销行为任意访问
}
}
用户账号逻辑处理 重写 UserDetailsService
package com.skypegmwcn.zhxy.skypegmwcn.service.impl;
import com.skypegmwcn.zhxy.skypegmwcn.entity.Admin;
import com.skypegmwcn.zhxy.skypegmwcn.mapper.AdminMapper;
import lombok.AllArgsConstructor;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
@AllArgsConstructor
public class CustomUserServicelmpl implements UserDetailsService {
private final AdminMapper adminMapper;
@Override
public UserDetails loadUserByUsername(String username) { //重写loadUserByUsername 方法获得 userdetails 类型用户
Admin admin = adminMapper.getAdmin();
String userName = admin.getUserName();
if(userName == null){
throw new UsernameNotFoundException("用户名不存在");
}
return new org.springframework.security.core.userdetails.User(admin.getUserName(),
admin.getPassWord(), AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
自定义验证码异常类
package com.skypegmwcn.zhxy.skypegmwcn.config;
import org.springframework.security.core.AuthenticationException;
public class ValidateCodeException extends AuthenticationException {
private static final long serialVersionUID = 5022575393500654458L;
ValidateCodeException(String message) {
super(message);
}
}
验证码拦截器 重写OncePerRequestFilter
package com.skypegmwcn.zhxy.skypegmwcn.config;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.filter.OncePerRequestFilter;
import org.thymeleaf.util.StringUtils;
import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class ValidateCodeFilter extends OncePerRequestFilter {
@Resource
private AuthenticationFailureHandler authenticationFailureHandler;
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
//这里是条件 路径为/api/user/login 并且为post请求才可以
if (StringUtils.equalsIgnoreCase("/api/user/login", httpServletRequest.getRequestURI())
&& StringUtils.equalsIgnoreCase(httpServletRequest.getMethod(), "post")) {
try {
validateCode(new ServletWebRequest(httpServletRequest));
} catch (ValidateCodeException e) {
authenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e);
return;
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
private void validateCode(ServletWebRequest servletWebRequest) throws ServletRequestBindingException, IOException {
//这个是之前存在session中的验证码
String captchaId = (String) servletWebRequest.getRequest().getSession().getAttribute("vrifyCode");
//这个是前端传过来的验证码
String codeInRequest = ServletRequestUtils.getStringParameter(servletWebRequest.getRequest(), "imageCode");
if (codeInRequest == null) {
throw new ValidateCodeException("验证码不能为空!");
}
if (!StringUtils.equalsIgnoreCase(captchaId, codeInRequest)) {
throw new ValidateCodeException("验证码不正确!");
}
}
}
登录成功返回给前台的json数据
package com.skypegmwcn.zhxy.skypegmwcn.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.skypegmwcn.zhxy.skypegmwcn.util.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component("MyAuthenticationSucessHandler")
public class MyAuthenticationSucessHandler implements AuthenticationSuccessHandler {
// private RequestCache requestCache = new HttpSessionRequestCache();
@Autowired
private ObjectMapper objectMapper;
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
//
// @Autowired
// private ObjectMapper mapper;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
//把authentication返回给前台
response.setContentType("application/json;charset=UTF-8");
//R.ok()为自定义的返回值
response.getWriter().write(objectMapper.writeValueAsString(R.ok()));
}
}
登录失败给前台的json返回值
package com.skypegmwcn.zhxy.skypegmwcn.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.skypegmwcn.zhxy.skypegmwcn.util.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component("myAuthenticationFailHandler")
public class MyAuthenticationFailHandler implements AuthenticationFailureHandler {
@Autowired
private ObjectMapper objectMapper;
//AuthenticationException记录,用户名没找到,密码没匹配上等信息 认证过程中所有发生的错误
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
System.out.println("登录失败");
System.out.println(exception);
exception.printStackTrace();
//把exception返回给前台
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(R.failed(exception.getMessage())));
}
}