我对你最大的帮助是给你一份无bug的代码,然后你自己去研究
一、pom.xml
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--kaptcha依赖:谷歌的图片验证码生成器--> <dependency> <groupId>com.github.axet</groupId> <artifactId>kaptcha</artifactId> <version>0.0.9</version> </dependency> <!--redis依赖:图片验证码需要用到一个缓存操作--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.18.1</version> </dependency> <!--Jwt依赖--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.15.0</version> </dependency> <!-- hutool工具类--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.3.3</version> </dependency> </dependencies>
二、数据库连接(application.properties)
server.port=8081 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/privateblog?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=123456 #开启后,hibernate会自动删除数据库数据(没有定义要插入的数据都会被删除)和更新数据库(同步实体类) spring.jpa.hibernate.ddl-auto=create spring.jpa.show-sql=true spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect spring.jpa.properties.hibernate.format_sql=true
三、创建实体类,orm用的springdatajpa,创建实体类即可同步到数据库
1.用户实体类(Sys_user.java)
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import javax.persistence.*; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import static javax.persistence.FetchType.EAGER; import static javax.persistence.GenerationType.AUTO; /** * 用户实体类 * @Author losen * @Date 2022/10/21 14:08 * @Version 1.0 */ @Entity @Data @NoArgsConstructor @AllArgsConstructor public class Sys_user { @Id @GeneratedValue(strategy = AUTO) private Long id; // id private String nickName; // 昵称 private String userName; // 用户名 private String passWord; // 密码 private String email; // 邮箱 private Date createdTime; // 创建时间 private String city; // 城市 private int status; // 帐号状态 private Date updatedTime; // 修改时间 private Date lastLogin; // 最后一次登录时间 @ManyToMany(fetch = EAGER) private Collection<Sys_role> roles = new ArrayList<>(); // 角色集合:多对多 }
2.角色实体类(Sys_role.java)
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import javax.persistence.*; import static javax.persistence.GenerationType.AUTO; /** * 角色实体类 * @author losen * @date 2022/10/21 14:09 * @version 1.0 */ @Entity @Data @NoArgsConstructor @AllArgsConstructor public class Sys_role { @Id @GeneratedValue(strategy = AUTO) private Long id; // id private String name; // 角色名称 private String remark; //角色备注 private int status; // 角色状态 }
四、创建mapper类
1.用户类数据库映射类(Sys_userRepo.java)
import com.doll.privateblog.domain.Sys_user; import org.springframework.data.jpa.repository.JpaRepository; /** * 操作用户映射 * @Author losen * @Date 2022/10/21 14:25 * @Version 1.0 */ public interface Sys_userRepo extends JpaRepository<Sys_user,Long> { /** * 根据用户名查询用户 * @param userName * @return */ Sys_user findByUserName(String userName); /** * 根据用户名删除用户 * @param userName * @return */ Integer deleteSys_userByUserName(String userName); }
2.角色实体类数据库映射类(Sys_roleRepo.java)
import com.doll.privateblog.domain.Sys_role; import org.springframework.data.jpa.repository.JpaRepository; /** * 操作角色映射 * @Author losen * @Date 2022/10/21 14:29 * @Version 1.0 */ public interface Sys_roleRepo extends JpaRepository<Sys_role,Long> { /** * 根据用户名查询角色 * @param name * @return */ Sys_role findByName(String name); }
五、创建接口类
1.用户接口类(Sys_userService.java)
import com.doll.privateblog.domain.Sys_role; import com.doll.privateblog.domain.Sys_user; import java.util.List; /** * 用户接口类 * @Author losen * @Date 2022/10/21 14:32 * @Version 1.0 */ public interface Sys_userService { Sys_user saveUser(Sys_user user); //添加用户 Sys_role saveRole(Sys_role role); //添加角色 void addRoleToUser(String userName,String roleName); //给用户添加角色 Sys_user getUser(String userName); //根据用户名获取单个用户对象 List<Sys_user> getUsers(); //获取多个用户对象 Integer removeByUserName(String userName); // 根据用户名删除用户 }
六、创建实现类
1.用户接口实现类()
import com.doll.privateblog.domain.Sys_role; import com.doll.privateblog.domain.Sys_user; import com.doll.privateblog.repo.Sys_roleRepo; import com.doll.privateblog.repo.Sys_userRepo; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import javax.transaction.Transactional; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * 用户接口实现类 * @Author losen * @Date 2022/10/21 14:40 * @Version 1.0 */ @Service @RequiredArgsConstructor @Transactional @Slf4j public class Sys_userServiceImpl implements Sys_userService, UserDetailsService { private final Sys_userRepo userRepo; private final Sys_roleRepo roleRepo; private final PasswordEncoder passwordEncoder; /** * 重写登录验证逻辑 * @param username * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { Sys_user user = userRepo.findByUserName(username); if(user == null){ log.error("User not found in the database"); throw new UsernameNotFoundException("User not found in the database"); }else { log.info("User found in the database : {}", username); } Collection<SimpleGrantedAuthority> authorities = new ArrayList<>(); user.getRoles().forEach(role -> { authorities.add(new SimpleGrantedAuthority(role.getName())); }); return new org.springframework.security.core.userdetails.User(user.getUserName(),user.getPassWord(),authorities); } /** * 新增用户 * @param user * @return */ @Override public Sys_user saveUser(Sys_user user) { log.info("Saving new user {} to the database", user.getUserName()); user.setPassWord(passwordEncoder.encode(user.getPassWord())); return userRepo.save(user); } /** * 新增角色 * @param role * @return */ @Override public Sys_role saveRole(Sys_role role) { log.info("Saving new role {} to the database", role.getName()); return roleRepo.save(role); } /** * 给用户添加角色 * @param userName * @param roleName */ @Override public void addRoleToUser(String userName, String roleName) { log.info("Adding role {} to user {}", roleName,userName); Sys_user user = userRepo.findByUserName(userName); Sys_role role = roleRepo.findByName(roleName); user.getRoles().add(role); } /** * 根据用户名获取用户对象 * @param userName * @return */ @Override public Sys_user getUser(String userName) { log.info("Fetching user {}",userName); return userRepo.findByUserName(userName); } /** * 获取所有用户对象 * @return */ @Override public List<Sys_user> getUsers() { log.info("Fetching all user"); return userRepo.findAll(); } /** * 根据用户名删除用户 * @param userName * @return */ @Override public Integer removeByUserName(String userName) { return userRepo.deleteSys_userByUserName(userName); } }
七、初始化数据库数据
1.启动类中(application.java)
import com.doll.privateblog.domain.Sys_role; import com.doll.privateblog.domain.Sys_user; import com.doll.privateblog.service.Sys_userService; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import java.util.ArrayList; /** * 服务模块启动类 * @author losen * @Date 21/10/2022 * @since 1.0 */ @SpringBootApplication public class PrivateBlogServerApplication { public static void main(String [] args){ SpringApplication.run(PrivateBlogServerApplication.class,args); } /** * 注册密码加密Bean * @return */ @Bean PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } /** * 初始化数据库 */ @Bean CommandLineRunner run(Sys_userService userService){ return args -> { userService.saveRole(new Sys_role(null,"ROLE_USER","普通用户",0)); userService.saveRole(new Sys_role(null,"ROLE_MANAGER","管理员",0)); userService.saveRole(new Sys_role(null,"ROLE_ADMIN","ADMIN",0)); userService.saveRole(new Sys_role(null,"ROLE_SUPER_ADMIN","超级管理员",0)); userService.saveUser(new Sys_user(null,"losen","2375731426","123456","doll9946163@163.com",null,"深圳",0,null,null,new ArrayList<>())); userService.saveUser(new Sys_user(null,"losen2","2375731425","123456","doll9946163@163.com",null,"上海",0,null,null,new ArrayList<>())); userService.saveUser(new Sys_user(null,"losen3","2375731424","123456","doll9946163@163.com",null,"重庆",0,null,null,new ArrayList<>())); userService.saveUser(new Sys_user(null,"losen4","2375731426","123456","doll9946163@163.com",null,"北京",0,null,null,new ArrayList<>())); userService.addRoleToUser("2375731426","ROLE_SUPER_ADMIN"); userService.addRoleToUser("2375731425","ROLE_ADMIN"); userService.addRoleToUser("2375731424","ROLE_MANAGER"); userService.addRoleToUser("2375731423","ROLE_USER"); }; } }
八、创建控制层(Controller)
1.Sys_userResource.java
import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.DecodedJWT; import com.doll.privateblog.domain.Sys_role; import com.doll.privateblog.domain.Sys_user; import com.doll.privateblog.service.Sys_userService; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Data; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import org.apache.commons.codec.binary.Base64; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URI; import java.util.*; import java.util.stream.Collectors; import static org.springframework.http.HttpHeaders.AUTHORIZATION; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; /** * 用户接口类 * @Author losen * @Date 2022/10/21 14:54 * @Version 1.0 */ @RestController @RequestMapping("/api") @RequiredArgsConstructor @CrossOrigin(origins = "*") public class Sys_userResource { @Autowired private Sys_userService sysUserService; /** * 获取所有用户对象 * @return */ @GetMapping("/users") public ResponseEntity<List<Sys_user>>getUsers(){ return ResponseEntity.ok().body(sysUserService.getUsers()); } /** * 根据用户名获取用户对象 * @param sysUser * @return */ @GetMapping("/user") public ResponseEntity<Sys_user>getUser(@RequestParam Sys_user sysUser){ return ResponseEntity.ok().body(sysUserService.getUser(sysUser.getUserName())); } /** * 新增角色 * @param username * @param password * @return */ @SneakyThrows @PostMapping("/user/save") // 字符串接受主要是,前端要传递图片验证码的key回来与redis中key匹配,如果出现与实体对象不符的属性会出现对象转换异常 public Map<String,String>saveUser(String username,String password){ Sys_user sysUser = new Sys_user(); sysUser.setUserName(username); sysUser.setPassWord(password); URI uri = URI.create(ServletUriComponentsBuilder.fromCurrentContextPath().path("/api/user/save").toUriString()); Sys_user user = sysUserService.getUser(sysUser.getUserName()); Map<String,String> resultMap = new HashMap<>(); if(user == null){ // 判断该用户名是否存在 sysUserService.saveUser(sysUser); sysUserService.addRoleToUser(username,"ROLE_MANAGER"); //添加的用户初始角色都为ROLE_MANAGER resultMap.put("status","200"); // 不存在就可以进行save操作 }else{ resultMap.put("status","500"); // 返回500状态值 } return resultMap; } /** * 新增角色 * @param role * @return */ @PostMapping("/role/save") public ResponseEntity<Sys_role>saveRole(@RequestBody Sys_role role){ URI uri = URI.create(ServletUriComponentsBuilder.fromCurrentContextPath().path("/api/role/save").toUriString()); return ResponseEntity.created(uri).body(sysUserService.saveRole(role)); } /** * 给用户添加角色 * @param form * @return */ @PostMapping("/role/addtouser") public ResponseEntity<?>addRoleToUser(@RequestBody RoleToUserForm form){ sysUserService.addRoleToUser(form.getUsername(), form.getRoleName()); return ResponseEntity.ok().build(); } /** * 刷新token,jwt刷新token的方式是:前端登录成功后后端创建token返回给前端,刷新token需要前端携带refresh_token,可以在跳转页面时刷新token * @param request * @param response * @throws IOException */ @GetMapping("/token/refresh") public void refreshToken(HttpServletRequest request, HttpServletResponse response) throws IOException { Map<String,Object> tokens = new HashMap<>(); String authorizationHeader = request.getHeader(AUTHORIZATION); if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { try { String refresh_token = authorizationHeader.substring("Bearer ".length()); Algorithm algorithm = Algorithm.HMAC256("secret".getBytes()); JWTVerifier verifier = JWT.require(algorithm).build(); DecodedJWT decodedJWT = verifier.verify(refresh_token); String username = decodedJWT.getSubject(); Sys_user user = sysUserService.getUser(username); // 获取用户角色,让前端判断该角色是否可以进入该页面 List<String> collect = user.getRoles().stream().map(Sys_role::getName).collect(Collectors.toList()); String access_token = JWT.create() . withSubject(user.getUserName()) .withExpiresAt(new Date(System.currentTimeMillis()+10*60*1000)) .withIssuer(request.getRequestURI().toString()) .withClaim("roles",user.getRoles().stream().map(Sys_role::getName).collect(Collectors.toList())) .sign(algorithm); tokens.put("access_token",access_token); tokens.put("refresh_token",refresh_token); tokens.put("status","200"); tokens.put("roles",collect); // 返回角色信息 response.setContentType(APPLICATION_JSON_VALUE); new ObjectMapper().writeValue(response.getOutputStream(),tokens); }catch (Exception exception){ response.setHeader("error",exception.getMessage()); Map<String,String> error = new HashMap<>(); error.put("error_message",exception.getMessage()); response.setContentType(APPLICATION_JSON_VALUE); tokens.put("status","500"); new ObjectMapper().writeValue(response.getOutputStream(),tokens); } }else { tokens.put("status","500"); new ObjectMapper().writeValue(response.getOutputStream(),tokens); throw new RuntimeException("Refresh token is missing"); } } @GetMapping("/user/delete") public Map<String,String>addRoleToUser(String userName){ Map<String,String> resultMap= new HashMap<>(); sysUserService.removeByUserName(userName); resultMap.put("status","200"); return resultMap; } } @Data class RoleToUserForm{ private String username; private String roleName; }
2.图片验证码控制器(CaptchaResource.java)
import cn.hutool.core.lang.UUID;
import com.doll.privateblog.domain.Const;
import com.doll.privateblog.utils.RedisUtil;
import com.google.code.kaptcha.Producer;
import lombok.RequiredArgsConstructor;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* 图片验证码接口
* @author losen
* @version 1.0
* @DateTime 2022/10/22 14:36
*/
@RestController @RequestMapping("/api") @RequiredArgsConstructor
public class CaptchaResource {
@Autowired
private RedisUtil redisUtil;
@Autowired
Producer producer;
@GetMapping("/captcha")
public Map<String,Object> Captcha(HttpServletResponse response) throws IOException {
String key = UUID.randomUUID().toString();
String code = producer.createText();
BufferedImage image = producer.createImage(code);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(image,"png",outputStream);
byte[] imageByte = outputStream.toByteArray();
String str = "data:image/png;base64,";
// String base64Img = str + encoder.encode(outputStream.toByteArray());
redisUtil.hset("captcha", key, code, 120);
Base64 base64 = new Base64();
Map<String,Object> captchaMap = new HashMap<>();
captchaMap.put("image",base64.encodeToString(imageByte));
captchaMap.put("uuid",key);
return captchaMap;
}
}
九、创建配置类和工具类
1.跨域配置类(CorsConfig.java)
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; import java.util.ArrayList; import java.util.List; /** * 跨域配置类 * @author losen * @date 2022/10/22 14:31 * @since 1.0.0 */ @Configuration public class CorsConfig { private CorsConfiguration buildConfig() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.setAllowCredentials(true); //sessionid 多次访问一致 // 允许访问的客户端域名 List<String> allowedOriginPatterns = new ArrayList<>(); allowedOriginPatterns.add("*"); corsConfiguration.setAllowedOriginPatterns(allowedOriginPatterns); corsConfiguration.addAllowedOriginPattern("*"); // 允许任何域名使用 corsConfiguration.addAllowedHeader("*"); // 允许任何头 corsConfiguration.addAllowedMethod("*"); // 允许任何方法(post、get等) return corsConfiguration; } @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", buildConfig()); // 对接口配置跨域设置 return new CorsFilter(source); } }
2.过滤器配置类(SecurityConfig.java)
import com.doll.privateblog.filter.CustomAuthenticationFilter; import com.doll.privateblog.filter.CustomAuthorizationFilter; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; 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.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import static org.springframework.http.HttpMethod.GET; import static org.springframework.http.HttpMethod.POST; import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS; /** * 过滤器配置器 * @Author losen * @Date 2022/10/21 16:21 * @Version 1.0 */ @Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig extends WebSecurityConfigurerAdapter { private final UserDetailsService userDetailsService; private final BCryptPasswordEncoder bCryptPasswordEncoder; private final CaptchaFilter captchaFilter; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception{ auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder); } @Override protected void configure(HttpSecurity http)throws Exception{ CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(authenticationManagerBean()); customAuthenticationFilter.setFilterProcessesUrl("/api/login"); http.sessionManagement().sessionCreationPolicy(STATELESS); http.authorizeRequests().antMatchers("/api/captcha","/api/user/save","/api/login/**","/api/token/refresh/**","/api/makeLineAndShapeChart","/api/users").permitAll(); // 设置不用拦截的api // http.authorizeRequests().antMatchers(GET,"/api/user/**").hasAnyAuthority("ROLE_USER"); // http.authorizeRequests().antMatchers(POST,"/api/user/save/**").hasAnyAuthority("ROLE_ADMIN"); http.authorizeRequests().anyRequest().authenticated(); http.addFilter(customAuthenticationFilter); http.addFilter(customAuthenticationFilter); http.addFilterBefore(new CustomAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class); http.addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class); // 添加验证码拦截校验过滤器 http.cors().and().csrf().disable(); // 关闭cors和csrf(防止跨域伪造攻击) } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
3.图片验证码样式配置类(KaptchaConfig.java)
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; /** * 验证码图片样式配置 * @author losen * @date 2022/10/22 13:43 * @since 1.0.0 */ @Configuration public class KaptchaConfig { @Bean DefaultKaptcha producer(){ Properties properties = new Properties(); properties.put("kaptcha.border","no"); properties.put("kaptcha.textproducer.font.color","black"); properties.put("kaptcha.textproducer.char.space","4"); properties.put("kaptcha.image.height","40"); properties.put("kaptcha.image.width","120"); properties.put("kaptcha.textproducer.font.size","30"); Config config = new Config(properties); DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); defaultKaptcha.setConfig(config); return defaultKaptcha; } }
4.图片验证码内容配置类(CaptchaUtil.java)
import cn.hutool.core.codec.Base64; import lombok.extern.slf4j.Slf4j; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Random; /** * 图片验证码内容配置类 * @author losen * @version 1.0 * @DateTime 2022/10/24 13:39 */ @Slf4j public class CaptchaUtil { //验证码个数 private int count=4; //验证码宽度,且设置每个字的宽度 private int width=count*50; //验证码高度 private int height=50; //图片验证码key private String code=""; //bufferedImage private BufferedImage bufferedImage; public CaptchaUtil() { } public CaptchaUtil(int count, int width, int height) { this.count = count; this.width = width; this.height = height; } public int getCount() { return count; } public String getCode() { return code; } public int getWidth() { return width; } public int getHeight() { return height; } public void setCount(int count) { this.count = count; width=this.count*50; } public void setWidth(int width) { this.width = width; } public void setHeight(int height) { this.height = height; } //测试写入 public static void main(String[] args){ long startend=System.currentTimeMillis(); CaptchaUtil captchaUtil =new CaptchaUtil(); //默认验证码位数为4,我这里设为5 captchaUtil.setCount(5); //得到缓冲区 BufferedImage image = captchaUtil.getImage(); //得到真实验证码 String code= captchaUtil.getCode(); long endTime=System.currentTimeMillis(); System.out.println("验证码为:"+code+"\n花费时间为:"+(endTime-startend)+"\n到E盘根目录下看,文件名为11.jpg"); } public BufferedImage getImage(){ //图片缓冲区 BufferedImage image = new BufferedImage(width,height,1); //获得笔 Graphics graphics = image.getGraphics(); //设置初始画笔为白色 graphics.setColor(new Color(255,255,254)); //画满整个图,也就是把图片先变为白色 graphics.fillRect(0,0,width,height); Random rd=new Random(); //设置字体 Font font=new Font("宋体",Font.PLAIN,35+rd.nextInt(10)); graphics.setFont(font); char[] chars="qweCRYHrtasdfBxy678934VTGopNUFKuighjklzSXEDLOP12cvbnmQAZWJMI50".toCharArray(); //画验证码 for (int i = 0; i <count ; i++) { String string=""; string+=chars[rd.nextInt(chars.length)]+""; graphics.setColor(new Color(rd.nextInt(254),rd.nextInt(254),rd.nextInt(254))); graphics.drawString(string,55*i+rd.nextInt(10),27+rd.nextInt(15)); code+=string; } //干扰点 for (int i = 0; i <25*count ; i++) { graphics.setFont(new Font("宋体",Font.PLAIN,15)); String string="."; graphics.setColor(new Color(rd.nextInt(255),rd.nextInt(255),rd.nextInt(255))); graphics.drawString(string,rd.nextInt(width),rd.nextInt(height)); } //干扰线 for (int i = 0; i <count+count/2 ; i++) { graphics.setFont(new Font("宋体",Font.PLAIN,10)); graphics.setColor(new Color(rd.nextInt(255),rd.nextInt(255),rd.nextInt(255))); graphics.drawLine(rd.nextInt(width),rd.nextInt(height),rd.nextInt(width),rd.nextInt(height)); } //归还笔 graphics.dispose(); //写到流里面需要用到ImageIo //这里做的测试,在本地测试下是否画的是那回事 /*try { ImageIO.write(image,"jpg",new FileOutputStream("E:/11.jpg")); } catch (IOException e) { e.printStackTrace(); }*/ this.bufferedImage=image; return image; } /** * @author: 执着(zlm) * @Date: 2020/9/27 23:37 * @Description: 将图片缓冲区转成base64编码 */ public static String getBase64(BufferedImage image){ String base64 = null; try { //输出流 ByteArrayOutputStream stream = new ByteArrayOutputStream(); ImageIO.write(image, "png", stream); base64 = Base64.encode(stream.toByteArray()); log.info("生成的图片验证码base64:{}",base64); } catch (IOException e) { log.error("生成生成的图片验证码base64失败:{}",e.getMessage()); e.printStackTrace(); } return base64; } }
5.Redis工具类(RedisUtil.java)
import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.util.CollectionUtils; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; /** * Redis工具类 * @author losen * @DateTime 2022/10/22 13:43 * @since 1.0.0 */ @RequiredArgsConstructor @Configuration public class RedisUtil { private final RedisTemplate redisTemplate; // =============================common============================ /** * 指定缓存失效时间 * @param key 键 * @param time 时间(秒) * @return */ public boolean expire(String key, long time) { try { if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根据key 获取过期时间 * @param key 键 不能为null * @return 时间(秒) 返回0代表为永久有效 */ public long getExpire(String key) { return redisTemplate.getExpire(key, TimeUnit.SECONDS); } /** * 判断key是否存在 * @param key 键 * @return true 存在 false不存在 */ public boolean hasKey(String key) { try { return redisTemplate.hasKey(key); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 删除缓存 * @param key 可以传一个值 或多个 */ @SuppressWarnings("unchecked") public void del(String... key) { if (key != null && key.length > 0) { if (key.length == 1) { redisTemplate.delete(key[0]); } else { redisTemplate.delete(CollectionUtils.arrayToList(key)); } } } // ============================String============================= /** * 普通缓存获取 * @param key 键 * @return 值 */ public Object get(String key) { return key == null ? null : redisTemplate.opsForValue().get(key); } /** * 普通缓存放入 * @param key 键 * @param value 值 * @return true成功 false失败 */ public boolean set(String key, Object value) { try { redisTemplate.opsForValue().set(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 普通缓存放入并设置时间 * @param key 键 * @param value 值 * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 * @return true成功 false 失败 */ public boolean set(String key, Object value, long time) { try { if (time > 0) { redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); } else { set(key, value); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 递增 * @param key 键 * @param delta 要增加几(大于0) * @return */ public long incr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递增因子必须大于0"); } return redisTemplate.opsForValue().increment(key, delta); } /** * 递减 * @param key 键 * @param delta 要减少几(小于0) * @return */ public long decr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递减因子必须大于0"); } return redisTemplate.opsForValue().increment(key, -delta); } // ================================Map================================= /** * HashGet * @param key 键 不能为null * @param item 项 不能为null * @return 值 */ public Object hget(String key, String item) { return redisTemplate.opsForHash().get(key, item); } /** * 获取hashKey对应的所有键值 * @param key 键 * @return 对应的多个键值 */ public Map<Object, Object> hmget(String key) { return redisTemplate.opsForHash().entries(key); } /** * HashSet * @param key 键 * @param map 对应多个键值 * @return true 成功 false 失败 */ public boolean hmset(String key, Map<String, Object> map) { try { redisTemplate.opsForHash().putAll(key, map); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * HashSet 并设置时间 * @param key 键 * @param map 对应多个键值 * @param time 时间(秒) * @return true成功 false失败 */ public boolean hmset(String key, Map<String, Object> map, long time) { try { redisTemplate.opsForHash().putAll(key, map); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 向一张hash表中放入数据,如果不存在将创建 * @param key 键 * @param item 项 * @param value 值 * @return true 成功 false失败 */ public boolean hset(String key, String item, Object value) { try { redisTemplate.opsForHash().put(key, item, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 向一张hash表中放入数据,如果不存在将创建 * @param key 键 * @param item 项 * @param value 值 * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 * @return true 成功 false失败 */ public boolean hset(String key, String item, Object value, long time) { try { redisTemplate.opsForHash().put(key, item, value); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 删除hash表中的值 * @param key 键 不能为null * @param item 项 可以使多个 不能为null */ public void hdel(String key, Object... item) { redisTemplate.opsForHash().delete(key, item); } /** * 判断hash表中是否有该项的值 * @param key 键 不能为null * @param item 项 不能为null * @return true 存在 false不存在 */ public boolean hHasKey(String key, String item) { return redisTemplate.opsForHash().hasKey(key, item); } /** * hash递增 如果不存在,就会创建一个 并把新增后的值返回 * @param key 键 * @param item 项 * @param by 要增加几(大于0) * @return */ public double hincr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, by); } /** * hash递减 * @param key 键 * @param item 项 * @param by 要减少记(小于0) * @return */ public double hdecr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, -by); } // ============================set============================= /** * 根据key获取Set中的所有值 * @param key 键 * @return */ public Set<Object> sGet(String key) { try { return redisTemplate.opsForSet().members(key); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 根据value从一个set中查询,是否存在 * @param key 键 * @param value 值 * @return true 存在 false不存在 */ public boolean sHasKey(String key, Object value) { try { return redisTemplate.opsForSet().isMember(key, value); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将数据放入set缓存 * @param key 键 * @param values 值 可以是多个 * @return 成功个数 */ public long sSet(String key, Object... values) { try { return redisTemplate.opsForSet().add(key, values); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 将set数据放入缓存 * @param key 键 * @param time 时间(秒) * @param values 值 可以是多个 * @return 成功个数 */ public long sSetAndTime(String key, long time, Object... values) { try { Long count = redisTemplate.opsForSet().add(key, values); if (time > 0) { expire(key, time); } return count; } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 获取set缓存的长度 * @param key 键 * @return */ public long sGetSetSize(String key) { try { return redisTemplate.opsForSet().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 移除值为value的 * @param key 键 * @param values 值 可以是多个 * @return 移除的个数 */ public long setRemove(String key, Object... values) { try { Long count = redisTemplate.opsForSet().remove(key, values); return count; } catch (Exception e) { e.printStackTrace(); return 0; } } // ===============================list================================= /** * 获取list缓存的内容 * @param key 键 * @param start 开始 * @param end 结束 0 到 -1代表所有值 * @return */ public List<Object> lGet(String key, long start, long end) { try { return redisTemplate.opsForList().range(key, start, end); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 获取list缓存的长度 * @param key 键 * @return */ public long lGetListSize(String key) { try { return redisTemplate.opsForList().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 通过索引 获取list中的值 * @param key 键 * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 * @return */ public Object lGetIndex(String key, long index) { try { return redisTemplate.opsForList().index(key, index); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 将list放入缓存 * @param key 键 * @param value 值 * @return */ public boolean lSet(String key, Object value) { try { redisTemplate.opsForList().rightPush(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * @param key 键 * @param value 值 * @param time 时间(秒) * @return */ public boolean lSet(String key, Object value, long time) { try { redisTemplate.opsForList().rightPush(key, value); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * @param key 键 * @param value 值 * @return */ public boolean lSet(String key, List<Object> value) { try { redisTemplate.opsForList().rightPushAll(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 * @param time 时间(秒) * @return */ public boolean lSet(String key, List<Object> value, long time) { try { redisTemplate.opsForList().rightPushAll(key, value); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根据索引修改list中的某条数据 * @param key 键 * @param index 索引 * @param value 值 * @return */ public boolean lUpdateIndex(String key, long index, Object value) { try { redisTemplate.opsForList().set(key, index, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 移除N个值为value * @param key 键 * @param count 移除多少个 * @param value 值 * @return 移除的个数 */ public long lRemove(String key, long count, Object value) { try { Long remove = redisTemplate.opsForList().remove(key, count, value); return remove; } catch (Exception e) { e.printStackTrace(); return 0; } } }
6.Redis配置类(RedisConfig.java)
import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; /** * Redis配置类 * @author losen * @date 2022/10/22 14:32 * @since 1.0.0 */ @Configuration public class RedisConfig { //调整redis序列化方式后写入缓存 @Bean RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){ RedisTemplate redisTemplate = new RedisTemplate(); redisTemplate.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); jackson2JsonRedisSerializer.setObjectMapper(new ObjectMapper()); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); return redisTemplate; } }
十、过滤器(Filter)
1.图片验证码过滤器(CaptchaFilter.java)
import com.doll.privateblog.utils.RedisUtil; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Configuration; import org.springframework.web.filter.OncePerRequestFilter; import javax.security.sasl.AuthenticationException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 图片验证码校验过滤器 * @author losen * @DateTime 2022/10/22 13:43 * @version 1.0 */ @Slf4j @RequiredArgsConstructor @Configuration public class CaptchaFilter extends OncePerRequestFilter { private static final String CAPTCHA_KEY = "captcha"; private final RedisUtil redisUtil; @SneakyThrows @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws AuthenticationException, IOException { String url = request.getRequestURI(); if(("/api/login".equals(url) && request.getMethod().equals("POST")) || ("/api/user/save".equals(url) && request.getMethod().equals("POST"))){ // 如果是登录接口,就要进行拦截-校验-过滤,校验验证码是否正确 try{ // 如果调用验证码校验方法有错,进行捕获 validate(request); // 调用验证码校验方法 }catch (AuthenticationException e){ log.error("Verification code failed"); throw new AuthenticationException(e.getMessage()); // 验证失败,具体错误要到方法内报错信息 } } filterChain.doFilter(request,response); // 拦截-校验-过滤之后,放行 } //校验验证逻辑 private void validate(HttpServletRequest request) throws AuthenticationException { String code = request.getParameter("code"); String key = request.getParameter("key"); System.out.println("key:"+key); Object hget = redisUtil.hget(CAPTCHA_KEY, key); if(code == null || key == null){ log.error("No verification code is received"); // 没有接收到验证码 throw new AuthenticationException("No verification code is received"); } if(!code.equals(redisUtil.hget(CAPTCHA_KEY,key))){ log.error("Verification code error"); // 验证码错误 throw new AuthenticationException("Verification code error"); } redisUtil.hdel(CAPTCHA_KEY,key); // 验证正确之后,验证码作废,将redis里的验证码删除,一次性使用 } }
2.用户登录过滤器(CustomAuthenticationFilter.java)
import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.binary.Base64; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; /** * 用户登录校验过滤器 * @Author losen * @Date 2022/10/21 16:43 * @Version 1.0 */ @Slf4j public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private static final String APPLICATION_JSON_VALUE = "application/json"; private final AuthenticationManager authenticationManager; public CustomAuthenticationFilter(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } @SneakyThrows @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { String name = request.getParameter("username"); String pwd = request.getParameter("password"); log.info("Username is: {}", name); log.info("password is: {}", pwd); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(name,pwd); return authenticationManager.authenticate(authenticationToken); } @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException { User user = (User) authentication.getPrincipal(); Algorithm algorithm = Algorithm.HMAC256("secret".getBytes()); String access_token = JWT.create() .withSubject(user.getUsername()) .withExpiresAt(new Date(System.currentTimeMillis()+10*60*1000)) .withIssuer(request.getRequestURI().toString()) .withClaim("roles",user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList())) .sign(algorithm); String refresh_token = JWT.create() .withSubject(user.getUsername()) .withExpiresAt(new Date(System.currentTimeMillis()+30*60*1000)) .withIssuer(request.getRequestURI().toString()) .sign(algorithm); // response.setHeader("access_token",access_token); // response.setHeader("refresh_token",refresh_token); // Map<String,String> tokens = new HashMap<>(); tokens.put("access_token",access_token); tokens.put("refresh_token",refresh_token); tokens.put("status","200"); response.setContentType(APPLICATION_JSON_VALUE); new ObjectMapper().writeValue(response.getOutputStream(),tokens); } }
3.请求过滤器(CustomAuthorizationFilter.java)
import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.DecodedJWT; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; 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; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; import static java.util.Arrays.stream; import static org.springframework.http.HttpHeaders.AUTHORIZATION; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; /** *请求过滤器(前端大部分请求都要先在这里校验) * @Author losen * @Date 2022/10/21 18:51 * @Version 1.0 */ @Slf4j public class CustomAuthorizationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { //除了登录和刷新token请求不用刷新token if (request.getServletPath().equals("/api/login") || request.getServletPath().equals("/api/token/refresh")) { filterChain.doFilter(request,response); }else { String authorizationHeader = request.getHeader(AUTHORIZATION); if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { try { String token = authorizationHeader.substring("Bearer ".length()); Algorithm algorithm = Algorithm.HMAC256("secret".getBytes()); JWTVerifier verifier = JWT.require(algorithm).build(); DecodedJWT decodedJWT = verifier.verify(token); String username = decodedJWT.getSubject(); String[] roles = decodedJWT.getClaim("roles").asArray(String.class); Collection<SimpleGrantedAuthority> authorities = new ArrayList<>(); stream(roles).forEach(role -> { authorities.add(new SimpleGrantedAuthority(role)); }); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username,null,authorities); SecurityContextHolder.getContext().setAuthentication(authenticationToken); filterChain.doFilter(request,response); }catch (Exception exception){ log.error("Error logging in: {}",exception.getMessage()); response.setHeader("error",exception.getMessage()); Map<String,String> error = new HashMap<>(); error.put("error_message",exception.getMessage()); response.setContentType(APPLICATION_JSON_VALUE); new ObjectMapper().writeValue(response.getOutputStream(),error); } }else { filterChain.doFilter(request,response); } } } }
完毕!!!有问题亲留言!!!