Spring Security是Spring家族中的一员,Security基于Spring框架,提供了一套Web应用安全性的完整解决方案。
主要包括:
其核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是 Basic Authentication Filter 用来认证用户的身份,一个在spring security中一种过滤器处理一种认证方式。
项目中使用 Spring Security 框架实现登录功能
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>0.9.1version>
dependency>
# jwt配置
jwt:
# JWT存储的请求头
tokenHeader: Authorization
# JWT 加解密使用的密钥
secret: cloude-secret
# JWT的超期限时间(60*60*24)
expiration: 604800
# JWT 负载中拿到开头
tokenHead: Bearer
创建config.security目录,并且在其目录新建JwtTokenUtil.java文件
package com.chuci.server.config.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @Auther chuci
* @Data 2021-12-22 22:43
* @Description:
*/
@Component
public class JwtTokenUtil {
private static final String CLAIM_KEY_USERNAME = "sub"; //荷载 用户名
private static final String CLAIM_KEY_CREATED = "created"; //荷载 创建时间
// 通过配置获取
@Value("${jwt.secret}")
private String secret; //JWT密钥
@Value("${jwt.expiration}")
private Long expiration; //JWT失效时间
/**
* 根据用户信息生成token
* @param userDetails
* @return
*/
public String generateToken(UserDetails userDetails){
Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
/**
* 从token中获取用户名
* @param token
* @return
*/
public String getUserNameFromToken(String token){
String username;
try {
Claims claims = getClaimsFromToken(token); //根据token获取荷载
username = claims.getSubject();
}catch (Exception e){
username = null;
e.printStackTrace();
}
return username;
}
/**
* 判断token是否有效
* @param token
* @param userDetails
* @return
*/
public boolean validateToken(String token, UserDetails userDetails){
String username = getUserNameFromToken(token);
// 判断username是否一致以及token是否失效
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
}
/**
* 验证token是否可以被刷新
* @param token
* @return
*/
public boolean canRefresh(String token){
return !isTokenExpired(token); //token过期就可以被刷新了
}
/**
* 刷新token
* @param token
* @return
*/
public String refreshToken(String token){
Claims claims = getClaimsFromToken(token);
claims.put(CLAIM_KEY_CREATED, new Date()); //更新创建时间 达到刷新token的目的
return generateToken(claims);
}
/**
* 判断token是否失效
* @param token
* @return
*/
private boolean isTokenExpired(String token) {
Date expiredDate = getExpiredDateFromToken(token);
return expiredDate.before(new Date());
}
/**
* 从token中获取时间
* @param token
* @return
*/
private Date getExpiredDateFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims.getExpiration();
}
/**
* 从token中获取荷载
* @param token
* @return
*/
private Claims getClaimsFromToken(String token) {
Claims claims = null;
try {
claims = Jwts.parser() //转荷载
.setSigningKey(secret) //添加签名
.parseClaimsJws(token) //添加密钥
.getBody(); //拿到荷载
}catch (Exception e){
e.printStackTrace();
}
return claims;
}
/**
* 根据JWT生成token 私有 只需public String generateToken调用
* @param claims
* @return
*/
private String generateToken(Map<String, Object> claims){
return Jwts.builder() //jwts生成
.setClaims(claims) //荷载
.setExpiration(generateExporation()) //失效时间
.signWith(SignatureAlgorithm.HS512, secret) //签名
.compact(); //密钥
}
/**
* 生成token失效时间
* @return
*/
private Date generateExporation() {
// 系统当前时间 加失效时间
return new Date(System.currentTimeMillis() + expiration * 1000);
}
}
实现UserDetails,重写其方法,将所有返回类型改为 true
,但注意isEnabled()
方法是否启用账号,返回值返回Admin类中enable
属性值,因此isEnabled()
方法返回值为enabled
。
package com.chuci.server.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Collection;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
/**
*
*
*
*
* @author chuci
* @since 2021-12-22
*/
@TableName("t_admin")
@ApiModel(value = "Admin对象", description = "")
public class Admin implements Serializable, UserDetails {
private static final long serialVersionUID = 1L;
@ApiModelProperty("id")
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@ApiModelProperty("姓名")
private String name;
@ApiModelProperty("手机号码")
private String phone;
@ApiModelProperty("住宅电话")
private String telephone;
@ApiModelProperty("联系地址")
private String address;
@ApiModelProperty("是否启用")
private Boolean enabled;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("密码")
private String password;
@ApiModelProperty("用户头像")
private String userFace;
@ApiModelProperty("备注")
private String remark;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getTelephone() {
return telephone;
}
public void setTelephone(String telephone) {
this.telephone = telephone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUserFace() {
return userFace;
}
public void setUserFace(String userFace) {
this.userFace = userFace;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
/**
* 重写 实现 UserDetails
* @return
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled; //是否启用
}
public void setUsername(String username) {
this.username = username;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
/**
* 重写toString
* @return
*/
@Override
public String toString() {
return "Admin{" +
"id=" + id +
", name=" + name +
", phone=" + phone +
", telephone=" + telephone +
", address=" + address +
", enabled=" + enabled +
", username=" + username +
", password=" + password +
", userFace=" + userFace +
", remark=" + remark +
"}";
}
}
每次请求,为了前后端参数统一,规定公共返回对象SysResult
创建vo目录,并且新建SysResult.java。包含状态码code
, 返回信息message
以及返回对象data
。并创建success方法以及error方法。同是使用lombok生成无参全参构造方法以及get/set方法。
注:返回对象可返回任何对象类型。
package com.chuci.server.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 公共返回对象
*
* @Auther chuci
* @Data 2022-01-09 22:26
* @Description:
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysResult {
private long code; //状态码
private String message; //返回信息
private Object data; //返回对象
/**
* 请求成功返回对象
* @return
*/
public static SysResult success(){
return new SysResult(200, "服务请求成功", null);
}
public static SysResult success(String msg){
return new SysResult(200, msg, null);
}
public static SysResult success(String msg, Object data){
return new SysResult(200, msg, data);
}
/**
* 请求失败
* @return
*/
public static SysResult error(){
return new SysResult(500, "服务请求失败", null);
}
public static SysResult error(String msg){
return new SysResult(500, msg, null);
}
public static SysResult error(String msg, Object data){
return new SysResult(500, msg, data);
}
}
创建目录结构bean,并且新建文件AdminLoginParam.java文件用来登录时参数传递。若使用Admin实体进行登录实体对象,则传递的对象太大,所以在这里进行简化,暂时仅传递 用户名以及密码即可(后期还需要传递验证码)。
package com.chuci.server.bean;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 用户登录实体类
*
* @Auther chuci
* @Data 2022-01-10 15:08
* @Description:
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(value = "AdminLogin实体类", description = "")
public class AdminLoginParam {
@ApiModelProperty(value = "用户名", required = true)
private String username;
@ApiModelProperty(value = "密码", required = true)
private String password;
}
新建文件LoginController.java并进行登录代码控制层的编写,主要实现:
package com.chuci.server.controller;
import com.chuci.server.entity.Admin;
import com.chuci.server.service.IAdminService;
import com.chuci.server.bean.AdminLoginParam;
import com.chuci.server.vo.SysResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.security.Principal;
/**
* 登录
*
* @Auther chuci
* @Data 2022-01-10 15:12
* @Description:
*/
@Api(tags = "LoginController")
@RestController
public class LoginController {
@Autowired
private IAdminService adminService;
/**
* 进行登录操作,返回SysResult对象。data包含登录所需验证
* @param adminLoginParam
* @param request
* @return
*/
@ApiOperation(value = "登陆之后返回token")
@PostMapping("/login")
public SysResult login(@RequestBody AdminLoginParam adminLoginParam, HttpServletRequest request){
return adminService.login(adminLoginParam.getUsername(), adminLoginParam.getPassword(), adminLoginParam.getCode(), request);
}
/**
* 登录成功,获取当前登录对象所有信息
* @param principal
* @return
*/
@ApiOperation(value = "获取当前登录用户信息")
@GetMapping("/admin/info")
public Admin getAdminInfo(Principal principal){
if (principal == null){
return null;
}
String username = principal.getName();
Admin admin = adminService.getAdminByUserName(username);
admin.setPassword(null);
return admin;
}
/**
* 退出登录,前端删除token进行退出操作
* @return
*/
@ApiOperation(value = "退出登录")
@PostMapping("/logout")
public SysResult logout(){
return SysResult.success("注销成功");
}
}
由MybatisPlus代码生成器生成的所有Service接口,前缀均有“I”标注,即Admin实体的Service接口为IAdminService。
主要实现登录以及登录用户信息的查询。
public interface IAdminService extends IService<Admin> {
/**
* 登录之后返回token
*
* @param code
* @param username
* @param password
* @param request
* @return
*/
SysResult login(String username, String password, HttpServletRequest request);
/**
* 根据用户名获取用户信息
* @param username
* @return
*/
Admin getAdminByUserName(String username);
}
package com.chuci.server.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.chuci.server.config.security.JwtTokenUtil;
import com.chuci.server.entity.Admin;
import com.chuci.server.mapper.AdminMapper;
import com.chuci.server.service.IAdminService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chuci.server.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
*
* 服务实现类
*
*
* @author chuci
* @since 2021-12-22
*/
@Service
public class AdminServiceImpl extends ServiceImpl<AdminMapper, Admin> implements IAdminService {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private AdminMapper adminMapper;
@Value("${jwt.tokenHead}")
private String tokenHead;
/**
* 登陆之后返回token
*
* @param code
* @param username
* @param password
* @param request
* @return
*/
@Override
public SysResult login(String username, String password, String code, HttpServletRequest request) {
// 登录
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (userDetails == null || passwordEncoder.matches(password,userDetails.getPassword())){
return SysResult.error("用户密码不正确");
}
if(!userDetails.isEnabled()){
return SysResult.error("账号禁用,请联系管理员!");
}
// 更新登录用户对象
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
// 生成token
String token = jwtTokenUtil.generateToken(userDetails);
Map<String, String> tokenMap = new HashMap<>();
tokenMap.put("token", token);
tokenMap.put("tokenHead", tokenHead);
return SysResult.success("登录成功", tokenMap);
}
/**
* 根据用户名获取用户信息
* @param username
* @return
*/
@Override
public Admin getAdminByUserName(String username) {
return adminMapper.selectOne(
new QueryWrapper<Admin>()
.eq("username", username)
.eq("enabled", true));
}
}
新建文件 SecurityConfig.java
SpringSecurity的登录逻辑是通过UserDetailsService中的loadByUserName来实现的。重写userDetailsService()方法并重新实现configure(AuthenticationManagerBuilder auth)。configure(WebSecurity web)开放部分接口以及资源。configure(HttpSecurity http)使用JWT, 不需要csrf。
package com.chuci.server.config.security;
import com.chuci.server.entity.Admin;
import com.chuci.server.service.IAdminService;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @Auther chuci
* @Data 2022-01-10 22:34
* @Description: Security配置类
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private IAdminService adminService;
@Autowired
private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
@Autowired
private RestAuthorizationEntryPoint restAuthorizationEntryPoint;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
}
/**
* 开放以下接口网页资源不进行安全认证
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
"/login",
"/logout",
"/captcha",
"css/**",
"js/**",
"/webjars/**",
"/swagger-resources/**",
"/v2/api-docs/**",
"/index.html",
"/doc.html",
"favicon.ico"
);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 使用JWT, 不需要csrf
http.csrf()
.disable()
// 基于token, 不需要session
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 所有请求都需要认证
.anyRequest()
.authenticated()
.and()
.headers()
.cacheControl();
// 添加jwt 登录授权拦截器
http.addFilterBefore(jwtAuthencationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
// 添加自定义未授权未登录结果返回
http.exceptionHandling()
.accessDeniedHandler(restfulAccessDeniedHandler)
.authenticationEntryPoint(restAuthorizationEntryPoint);
}
@Override
@Bean
public UserDetailsService userDetailsService() {
return username -> {
Admin admin = adminService.getAdminByUserName(username);
if (admin != null) {
return admin;
}
return null;
};
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public JWTAuthencationTokenFilter jwtAuthencationTokenFilter() {
return new JWTAuthencationTokenFilter();
}
}
新建JWTAuthencationTokenFilter.java 文件
package com.chuci.server.config.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
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;
/**
* @Auther chuci
* @Data 2022-01-10 22:59
* @Description: JWT 登录授权过滤器
*/
public class JWTAuthencationTokenFilter extends OncePerRequestFilter {
@Value("${jwt.tokenHeader}")
private String tokenHeader;
@Value("${jwt.tokenHead}")
private String tokenHead;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader(tokenHeader);
// 存在token
if(authHeader != null && authHeader.startsWith(tokenHead)){
String authToken = authHeader.substring(tokenHead.length());
String username = jwtTokenUtil.getUserNameFromToken(authToken);
// token存在用户但未登录
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null){
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 验证token是否有效,重新设置用户对象
if (jwtTokenUtil.validateToken(authToken, userDetails)){
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
}
filterChain.doFilter(request, response);
}
}
package com.chuci.server.config.security;
import com.chuci.server.vo.SysResult;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @Auther chuci
* @Data 2022-01-14 22:06
* @Description:
*/
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json");
PrintWriter writer = httpServletResponse.getWriter();
SysResult result = SysResult.error("权限不足,请联系管理员");
result.setCode(403);
writer.write(new ObjectMapper().writeValueAsString(result));
writer.flush();
writer.close();
}
}
package com.chuci.server.config.security;
import com.chuci.server.vo.SysResult;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @Auther chuci
* @Data 2022-01-14 21:42
* @Description:
*/
@Component
public class RestAuthorizationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setCharacterEncoding("UTF-8"); //设置编码格式
httpServletResponse.setContentType("application/json"); //设置传输为JSON格式
PrintWriter writer = httpServletResponse.getWriter();
SysResult result = SysResult.error("尚未登录,请登录后再试!");
result.setCode(401);
writer.write(new ObjectMapper().writeValueAsString(result));
writer.flush();
writer.close();
}
}
在团队开发中,一个好的 API 文档不但可以减少大量的沟通成本,还可以帮助一位新人快速上手业务。传统的做法是由开发人员创建一份 RESTful API 文档来记录所有的接口细节,并在程序员之间代代相传。
这种做法存在以下几个问题:
API 接口众多,细节复杂,需要考虑不同的HTTP请求类型、HTTP头部信息、HTTP请求内容等,想要高质量的完成这份文档需要耗费大量的精力;
难以维护。随着需求的变更和项目的优化、推进,接口的细节在不断地演变,接口描述文档也需要同步修订,可是文档和代码处于两个不同的媒介,除非有严格的管理机制,否则很容易出现文档、接口不一致的情况
Swagger2 的出现就是为了从根本上解决上述问题。它作为一个规范和完整的框架,可以用于生成、描述、调用和可视化 RESTful 风格的 Web 服务:
接口文档在线自动生成,文档随接口变动实时更新,节省维护成本
支持在线接口测试,不依赖第三方工具
Swagger2自带UI不太好看,之后更换第三方UI界面
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.9.2version>
dependency>
<dependency>
<groupId>com.google.guavagroupId>
<artifactId>guavaartifactId>
<version>31.0.1-jreversion>
dependency>
<dependency>
<groupId>com.github.xiaoymingroupId>
<artifactId>knife4j-micro-spring-boot-starterartifactId>
<version>2.0.5version>
dependency>
<dependency>
<groupId>com.github.xiaoymingroupId>
<artifactId>knife4j-spring-boot-starterartifactId>
<version>2.0.5version>
dependency>
在config文件夹下创建新文件夹swagger存放Swagger2配置类。新建SwaggerConfig.java文件
在这里进行简单常用配置
package com.chuci.server.config.swagger;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.checkerframework.checker.units.qual.A;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
import java.util.List;
/**
* @Auther chuci
* @Data 2022-01-14 22:22
* @Description:
*/
@Configuration
@EnableSwagger2 //启用Swagger2
@EnableKnife4j //启用Knife4j
public class SwaggerConfig {
/**
* 通过@Configuration注解,让Spring来加载该类配置。
* 再通过@EnableSwagger2注解来启用Swagger2。
*
* 再通过createRestApi()函数创建Docket的Bean之后,
* apiInfo()用来创建该Api的基本信息(这些基本信息会展现在文档页面中)。
* select()函数返回一个ApiSelectorBuilder实例用来控制哪些接口暴露给Swagger来展现
* 采用扫描所有定义,Swagger会扫描所有Controller定义的API,并产生文档内容(除了被@ApiIgnore指定的请求)。
*
* @return
*/
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2) //标明文档类型 Swagger2
.apiInfo(apiInfo()) //apiInfo()用来创建Api的基本信息(这些信息会展现在文档页面中)
.groupName("CloudE_Server") //组名
.select() //select()函数返回一个ApiSelectorBuilder实例用来控制哪些接口暴露给Swagger来展现
.apis(RequestHandlerSelectors.basePackage("com.chuci.server.controller")) //扫描特定包下面的文件 还有.any 扫描所有包
.paths(PathSelectors.any()) //Swagger会扫描该包下的所有Controller定义的API,并产生文档内容(除了被@ApiIgnore定义的请求)
.build()
.securityContexts(securityContexts())
.securitySchemes(securitySchemes());
}
private List<? extends SecurityScheme> securitySchemes() {
// 设置请求头信息
List<ApiKey> res = new ArrayList<>();
ApiKey apiKey = new ApiKey("Authorization", "Authorization", "Header");
res.add(apiKey);
return res;
}
private List<SecurityContext> securityContexts() {
// 设置需要认证的路径
List<SecurityContext> res = new ArrayList<>();
res.add(getContextByPath("/hello/.*"));
return res;
}
private SecurityContext getContextByPath(String pathRegex) {
return SecurityContext.builder()
.securityReferences(defaultAuth())
.forPaths(PathSelectors.regex(pathRegex))
.build();
}
private List<SecurityReference> defaultAuth() {
List<SecurityReference> res = new ArrayList<>();
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
res.add(new SecurityReference("Authorization", authorizationScopes));
return res;
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("CloudE 接口文档")
.description("物华天宝 , 龙光射牛斗之墟 \r"
+ "人杰地灵 , 徐孺下陈蕃之榻\r"
+ "------CloudE 接口文档")
.termsOfServiceUrl("www.baidu.com")
.contact(new Contact("【楚辞】", "http://localhost:8081/doc.html", "自己邮箱地址"))
.version("1.0")
.build();
}
}
同时为了测试,我们新建了HelloController。java文件进行Swagger的测试
我们通过域名:端口号/doc,html
来访问swagger
这里采用谷歌的验证码解决方案
dependency>
<dependency>
<groupId>com.github.axetgroupId>
<artifactId>kaptchaartifactId>
<version>0.0.9version>
dependency>
新建CaptchaConfig配置文件,在这里进行验证码的一些设置,如边框,字体大小,字体样式等等。
package com.chuci.server.config.captcha;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
/**
* 验证码配置类
*
* @Auther chuci
* @Data 2022-01-15 18:04
* @Description:
*/
@Configuration
public class CaptchaConfig {
@Bean
public DefaultKaptcha defaultKaptcha() {
//验证码生成器
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
//配置
Properties properties = new Properties();
//是否有边框
properties.setProperty("kaptcha.border", "yes");
//设置边框颜色
properties.setProperty("kaptcha.border.color", "105,179,90");
//边框粗细度,默认为1
// properties.setProperty("kaptcha.border.thickness","1");
//验证码
properties.setProperty("kaptcha.session.key", "code");
//验证码文本字符颜色 默认为黑色
properties.setProperty("kaptcha.textproducer.font.color", "blue");
//设置字体样式
properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
//字体大小,默认40
properties.setProperty("kaptcha.textproducer.font.size", "30");
//验证码文本字符内容范围 默认为abced2345678gfynmnpwx
// properties.setProperty("kaptcha.textproducer.char.string", "");
//字符长度,默认为5
properties.setProperty("kaptcha.textproducer.char.length", "4");
//字符间距 默认为2
properties.setProperty("kaptcha.textproducer.char.space", "4");
//验证码图片宽度 默认为200
properties.setProperty("kaptcha.image.width", "100");
//验证码图片高度 默认为40
properties.setProperty("kaptcha.image.height", "40");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
package com.chuci.server.controller;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
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.IOException;
/**
* 验证码
*
* @Auther chuci
* @Data 2022-01-15 18:14
* @Description:
*/
@RestController
public class CaptchaController {
@Autowired
private DefaultKaptcha defaultKaptcha;
@ApiOperation(value = "验证码")
@GetMapping(value = "/captcha", produces = "image/jpeg")
public void captcha(HttpServletRequest request, HttpServletResponse response){
// 定义response输出类型为image/jpeg类型
response.setDateHeader("Expires", 0);
// Set standard HTTP/1.1 no-cache headers.
response.setHeader("Cache-Control", "no-store, no-cache, mustrevalidate");
// Set IE extended HTTP/1.1 no-cache headers (use addHeader).
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
// Set standard HTTP/1.0 no-cache header.
response.setHeader("Pragma", "no-cache");
// return a jpeg
response.setContentType("image/jpeg");
//-------------------生成验证码 begin --------------------------
//获取验证码文本内容
String text = defaultKaptcha.createText();
System.out.println("验证码:" + text);
//将验证码放在session中
request.getSession().setAttribute("captcha", text);
//根据文本内容创建图片验证码
BufferedImage image = defaultKaptcha.createImage(text);
ServletOutputStream outputStream = null;
try {
outputStream = response.getOutputStream();
//输出流输出图片,格式jpg
ImageIO.write(image, "jpg", outputStream);
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(outputStream != null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//-------------------生成验证码 end --------------------------
}
}
重启项目,我们可以看到swagger出现验证码模块,并且测试成功生成验证码
// 验证码检测
String captcha = request.getSession().getAttribute("captcha").toString();
System.out.println("captcha:" + captcha + "; code:" + code);
if(!StringUtils.hasLength(code) || !captcha.equalsIgnoreCase(code)){
return SysResult.error("验证码错误,请重新输入!");
}
"tokenHead": "Bearer"
"token": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImNyZWF0ZWQiOjE2NDI2NjUwMzc4OTYsImV4cCI6MTY0MzI2OTgzN30.iVkznvdJ3DCCVSdVbCBN34nz_BG1JGXyfolJ5GLH7_uIZvEcMHLAzc6q8Hkzqx8AX_d7VEH_wk20mrbtk3HvgA"
再次进行接口调用,则会带如刚刚的参数值token令牌进行登录验证。
致此,整个登录模块已完成,等待前端页面调用验证即可。
上一节: Cloud E随笔-后端_piece2–代码生成器
下一节:
此 系 列 以 完 整 记 录 自 己 项 目 经 历 此系列以完整记录自己项目经历 此系列以完整记录自己项目经历