接上文 SpringBoot整合Spring Security
MongoDB:5.0.6
SpringBoot:2.5.3
JDK:1.8
本文测试使用了 Swagger的增强工具knife4j 进行测试
依赖较多 这里全部粘贴出来
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-mongodbartifactId>
<version>2.6.4version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.9.2version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.9.2version>
dependency>
<dependency>
<groupId>com.github.xiaoymingroupId>
<artifactId>knife4j-spring-boot-starterartifactId>
<version>2.0.2version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-validationartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.79version>
dependency>
dependencies>
其中MongoDB 我是用来存储用户登录日志 你们可以删掉或者使用mysql 其中的swagger也是可以去掉的
package com.king.security.config;
import com.king.security.config.login.DefaultAuthenticationFailureHandler;
import com.king.security.config.login.DefaultAuthenticationSuccessHandler;
import com.king.security.config.login.LoginFilter;
import com.king.security.service.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @program: springboot
* @description:
* @author: King
* @create: 2022-03-13 08:28
*/
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启方法权限控制
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserServiceImpl userService;
@Autowired
private DefaultAuthenticationSuccessHandler defaultAuthenticationSuccessHandler;
@Autowired
private DefaultAuthenticationFailureHandler defaultAuthenticationFailureHandler;
@Autowired
LoginFilter loginFilter;
/**
* 加密方式
*/
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//配置自己实现的登录认证的service,并设置密码的加密方式()
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
//静态资源配置
@Override
public void configure(WebSecurity web) throws Exception {
//swagger2所需要用到的静态资源,允许访问
web.ignoring().antMatchers("/swagger/**")
.antMatchers("/swagger-ui.html")
.antMatchers("/webjars/**")
.antMatchers("/v2/**")
.antMatchers("/v2/api-docs-ext/**")
.antMatchers("/swagger-resources/**")
.antMatchers("/doc.html")
.antMatchers("/lib/**")
.antMatchers("/layer/**")
.antMatchers("/layui/**")
.antMatchers("/layui/css/**")
.antMatchers("/login.html")
.antMatchers("/index.html");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//关闭跨域保护
http.csrf().disable();
//验证码校验
http.addFilterBefore(loginFilter, UsernamePasswordAuthenticationFilter.class);
//放行验证码
http.authorizeRequests().antMatchers("/login_code.png").permitAll();
// 指定指定要的登录页面
http.formLogin().loginPage("/login").loginProcessingUrl("/api/user/login.do")
.successHandler(defaultAuthenticationSuccessHandler)
.failureHandler(defaultAuthenticationFailureHandler).permitAll();
http.authorizeRequests().anyRequest().fullyAuthenticated();
http.logout().logoutUrl("logout.do");
}
}
自定义处理登录成功
package com.king.security.config.login;
import com.alibaba.fastjson.JSON;
import com.king.security.entity.User;
import com.king.security.entity.UserLog;
import com.king.security.mapper.UserLogMapper;
import com.king.security.vo.ResultObj;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @program: springboot
* @description: 登录成功
* @author: King
* @create: 2022-03-13 08:41
*/
@Component
public class DefaultAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
UserLogMapper userLogMapper;
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication)
throws IOException {
User user = (User) authentication.getPrincipal();
UserLog log = UserLog.builder().uid(user.getId()).name(user.getName()).ip(getIpAddress(request)).build();
userLogMapper.save(log);
logger.info("----login in succcess----");
logger.info(log.toString());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(JSON.toJSONString(ResultObj.ok("登录成功!")));
}
//获取IP地址
public static String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
自定义处理登录失败
package com.king.security.config.login;
import com.alibaba.fastjson.JSON;
import com.king.security.vo.ResultObj;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @program: springboot
* @description:
* @author: King
* @create: 2022-03-13 08:43
*/
@Component
public class DefaultAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException e) throws IOException,
ServletException {
logger.info("login in failure : " + e.getMessage());
ResultObj resultObj = new ResultObj();
if (e instanceof LockedException) {
resultObj = ResultObj.error("账户被锁定,登录失败!");
} else if (e instanceof BadCredentialsException) {
resultObj = ResultObj.error("账户名或密码输入错误,登录失败!");
} else if (e instanceof DisabledException) {
resultObj = ResultObj.error("账户被禁用,登录失败!");
} else if (e instanceof AccountExpiredException) {
resultObj = ResultObj.error("账户已过期,登录失败!");
} else if (e instanceof CredentialsExpiredException) {
resultObj = ResultObj.error("账户已过期,登录失败!");
} else {
resultObj = ResultObj.error(e.getMessage());
}
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(JSON.toJSONString(resultObj));
}
}
}
package com.king.security.config.login;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
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;
/**
* @program: springboot
* @description: 验证码拦截器
* @author: King
* @create: 2022-03-12 06:16
*/
@Component
public class LoginFilter extends OncePerRequestFilter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private DefaultAuthenticationFailureHandler authenticationFailureHandler;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
//如果是 登录请求 则执行
if ((request.getMethod().equalsIgnoreCase("post") &&
request.getRequestURI().endsWith("login.do"))) {
try {
validate(request, response);
} catch (ValidateCodeException e) {
//调用错误处理器,最终调用自己的
authenticationFailureHandler.onAuthenticationFailure(request, response, e);
return;//结束方法,不再调用过滤器链
}
}
chain.doFilter(request, response);
}
public void validate(HttpServletRequest request, HttpServletResponse response) throws ValidateCodeException {
//请求中传来的验证码
String code = request.getParameter("login_code");
//session 存储的验证码
String session_Code = (String) request.getSession().getAttribute("login_code");
logger.info("用户输入验证码:{}========session中存的验证码:{}", code, session_Code);
if (StringUtils.isEmpty(code)) {
throw new ValidateCodeException("验证码不能为空!");
}
if (StringUtils.isEmpty(session_Code)) {
throw new ValidateCodeException("验证码已经失效!");
}
if (!session_Code.equalsIgnoreCase(code)) {
throw new ValidateCodeException("验证码输入错误!");
}
}
/**
* 验证码错误异常,继承spring security的认证异常
*/
public static class ValidateCodeException extends AuthenticationException {
/**
* @Fields serialVersionUID : TODO
*/
private static final long serialVersionUID = 1L;
public ValidateCodeException(String msg) {
super(msg);
}
}
}
package com.king.security.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @program: springboot
* @description:
* @author: King
* @create: 2022-03-02 00:34
*/
@Controller
public class HelloController {
@RequestMapping("login")
public String openLogin() {
return "login.html";
}
}
这里就是生成一个图片验证码传到前端 可以看看我之前的文章
package com.king.security.controller;
import com.king.security.util.VerifyCodeGen;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@RestController()
@Api(value = "图形验证码接口", tags = "图形验证码接口")
public class VerifyCodeController {
@ApiOperation(value = "获取登录图形验证码", tags = "图形验证码接口")
@GetMapping(value = "/login_code.png")
public void login(HttpServletRequest req, HttpServletResponse resp) throws IOException {
HttpSession session = req.getSession();
session.setAttribute("login_code", VerifyCodeGen.outputImage(resp));
}
}
这是一个测试类 其中@PreAuthorize 表示只有 ADMIN 权限的可以访问此接口
package com.king.security.controller;
import io.swagger.annotations.Api;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @program: springboot
* @description:
* @author: King
* @create: 2022-03-02 13:00
*/
@RestController
@RequestMapping("/api/admin")
@PreAuthorize("hasAuthority('ADMIN')")
@Api(value = "管理员操作接口",tags = "管理员操作接口")
public class AdminController {
@GetMapping("/hello")
public String hello(){
return "Hello Admin!";
}
}
同理 这里只有 USER 或者 ADMIN 权限可以访问
package com.king.security.controller;
import com.king.security.vo.ResultObj;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
* @program: springboot
* @description:
* @author: King
* @create: 2022-03-02 13:03
*/
@RestController
@RequestMapping("/api/user")
@PreAuthorize("hasAnyRole('ADMIN','USER')")
@Api(value = "用户操作接口",tags = "用户操作接口")
public class UserController {
@GetMapping("/hello")
public String hello() {
return "Hello User!";
}
/**
* 获得用户登录信息
*
* @return
*/
@ApiOperation(value = "获得用户登录信息", httpMethod = "GET")
@GetMapping("/info")
public Object info() {
return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
主要代码就这样全部代码可以在文末获取
前端 使用了一些框架 所以代码有点多就不全部粘贴出来了,文章末尾发源码地址
const app = new Vue({
el: "#main",
data: {
name: "",
password: "",
validateCode: ""
},
methods: {
login: function () {
let url = "/api/user/login.do";
let param = new URLSearchParams()
param.append('username', this.name)
param.append('password', this.password)
param.append('login_code', this.validateCode);
console.log(this.validateCode)
axios({
method: 'post',
url: url,
data: param
}).then(function (res) {
if (res.data.code === 1) {
alertLayer(res.data.msg,"index.html")
} else {
alertMy(res.data.msg)
}
console.log(res);
}).catch(function (error) {
console.log(error);
});
}
}
});
Github https://github.com/KingJin-web/springboot
Gitee https://gitee.com/KingJin-web/springboot