SpringBoot+SpringSecurity6+Jwt最小化代码

SpringBoot+SpringSecurity6+Jwt最小化代码

[toc]

零、参考资料

一、快速开始-认证

1.1、引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.cyw</groupId>
<artifactId>spring-security-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-security-demo</name>
<description>spring-security-demo</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<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>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- 以下依赖是为了解决高版本JDK+jjwt库时的报错 -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.4.0-b180830.0359</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>3.0.0-M4</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>3.0.0-M4</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

1.2、JwtUtil

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Date;
@Slf4j
@Component
public class JwtUtil {
//常量
public static final long EXPIRE = 1000 * 60 * 60 * 4; //token过期时间,4个小时
public static final String APP_SECRET = "amRiYzpteXNxbDovL2xvY2FsaG9zdDozMzA2L2RiX3NlY3VyaXR5P3VzZVNTTD1mYWxzZSZzZXJ2ZXJUaW1lem9uZT1VVEMmY2hhcmFjdGVyRW5jb2Rpbmc9dXRmOA=="; //秘钥
//生成token字符串的方法
public String getToken(String userName){
log.info(userName);
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
.setSubject("user")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
.claim("userName", userName)//设置token主体部分 ,存储用户信息
.signWith(SignatureAlgorithm.HS256, APP_SECRET)
.compact();
}
//验证token字符串是否是有效的 包括验证是否过期
public boolean checkToken(String jwtToken) {
if(jwtToken == null || jwtToken.isEmpty()){
log.error("Jwt is empty");
return false;
}
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
Claims body = claims.getBody();
if ( body.getExpiration().after(new Date(System.currentTimeMillis()))){
return true;
} else
return false;
} catch (Exception e) {
log.error(e.getMessage());
return false;
}
}
public Claims getTokenBody(String jwtToken){
if(jwtToken == null || jwtToken.isEmpty()){
log.error("Jwt is empty");
return null;
}
try {
return Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken).getBody();
} catch (Exception e){
log.error(e.getMessage());
return null;
}
}
}

1.3、JwtFilter

import com.cyw.springsecuritydemo.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import jakarta.annotation.Resource;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
@Slf4j
public class JwtFilter extends OncePerRequestFilter {
@Resource
private JwtUtil jwtUtil;
@Resource
private MyUserDetailsService myUserDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String jwtToken = request.getHeader("token");//从请求头中获取token
if (jwtToken != null && !jwtToken.isEmpty() && jwtUtil.checkToken(jwtToken)){
try {//token可用
Claims claims = jwtUtil.getTokenBody(jwtToken);
String userName = (String) claims.get("userName");
UserDetails user = myUserDetailsService.loadUserByUsername(userName);
if (user != null){
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (Exception e){
log.error(e.getMessage());
}
}else {
log.warn("jwt为空,请确定是否要登录");
}
filterChain.doFilter(request, response);//继续过滤
}
}

1.4、MyUser(UserDetails的实现类)

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;
public class MyUser extends User {
// todo 其他自定义的字段
public MyUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, true,true,true,true,authorities);
}
public MyUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
}
}

1.5、AuthController 登录接口

import com.cyw.springsecuritydemo.pojo.Rst;
import com.cyw.springsecuritydemo.pojo.vo.LoginVo;
import com.cyw.springsecuritydemo.utils.JwtUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
public class AuthController{
@Resource
AuthenticationManager authenticationManager;
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/auth/login")
public Rst login(LoginVo loginVo) {
if (loginVo==null || !StringUtils.hasText(loginVo.getUsername())) {
return Rst.err("用户名或密码不能为空!");
}
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginVo.getUsername(), loginVo.getPassword());
Authentication authentication = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
MyUser myUser = (MyUser) authentication.getPrincipal();
String jwtToken = jwtUtil.getToken(myUser.getUsername());
return Rst.ok("登录成功",jwtToken);
}
}

1.6、MyUserDetailsService(UserDetailsService的实现类)

import com.cyw.springsecuritydemo.mapper.TbUserMapper;
import com.cyw.springsecuritydemo.pojo.TbUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
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.Component;
import java.util.List;
@Slf4j
@Component
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private TbUserMapper tbUserMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("进入了 MyUserDetailsService 类");
TbUser tbUser = tbUserMapper.findUserWithAuthority(username);
if (tbUser == null) {
throw new UsernameNotFoundException("该用户不存在");
}
List<GrantedAuthority> grantedAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList(tbUser.getAuthorityListStr());
return new MyUser(username, tbUser.getPwd(), grantedAuthorities);
}
}

1.8、SpringSecurity的配置类

import com.cyw.springsecuritydemo.auth.JwtFilter;
import com.cyw.springsecuritydemo.auth.MyUserDetailsService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@EnableWebSecurity
@Configuration
public class SecurityConfig {
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public AuthenticationProvider authenticationProvider(BCryptPasswordEncoder bCryptPasswordEncoder,MyUserDetailsService myUserDetailsService){
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(bCryptPasswordEncoder);
provider.setUserDetailsService(myUserDetailsService);
return provider;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationProvider authenticationProvider, JwtFilter jwtFilter) throws Exception {
http
.formLogin(AbstractHttpConfigurer::disable) //取消默认登录页面的使用
.logout(AbstractHttpConfigurer::disable) //取消默认登出页面的使用
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/auth/login","/auth/register").permitAll() // 允许访问登录接口、注册接口
.anyRequest().authenticated() // 其他接口需要登录才能访问
)
.authenticationProvider(authenticationProvider)
.csrf(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable)
.sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
// .exceptionHandling((exceptions) -> exceptions
// .authenticationEntryPoint(new MyAuthenEntryPoint()) // 认证失败
// .accessDeniedHandler(new MyAccessDeniedHandler()) // 授权失败
// )
;
return http.build();
}
}

登录表单的配置:

http
.formLogin(form -> {
form.usernameParameter("userName")
.passwordParameter("pwd")
.successHandler(new MyLoginSuccessHandler())
.failureHandler(new MyLoginFailHandler());
})//取消默认登录页面的使用

1.9、UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cyw.springsecuritydemo.mapper.TbUserMapper">
<resultMap id="rstMap" type="com.cyw.springsecuritydemo.pojo.TbUser">
<id column="id" property="id"/>
<result column="user_name" property="userName"/>
<result column="pwd" property="pwd"/>
<result column="perm_names" property="authorityListStr"/>
</resultMap>
<select id="findUserWithAuthority" resultMap="rstMap">
SELECT DISTINCT u.id,
u.user_name,
u.pwd,
GROUP_CONCAT(distinct r.role_name SEPARATOR ',') AS role_names,
GROUP_CONCAT(distinct p.perm_name SEPARATOR ',') AS perm_names
FROM tb_user u
LEFT JOIN tb_mid_user_role ur ON u.id = ur.uid
left join tb_role r on ur.role_id = r.id
LEFT JOIN tb_mid_role_perm rp ON ur.role_id = rp.role_id
LEFT JOIN tb_perm p ON p.id = rp.perm_id
<where>
u.del=0
<if test="userName != null and userName != ''">
and user_name = #{userName}
</if>
</where>
GROUP BY u.id
</select>
</mapper>

1.10、Postman测试

a. POST方式(x-www-form-urlencoded) {{baseUrl}}/auth/login接口
b. 后置操作(接口页签中的Tests选项卡):

let jsonData = pm.response.json();
// console.log(jsonData.data)
pm.collectionVariables.set("jwt", jsonData.data);

c. 其它接口的请求头加一个token头:

token: {{jwt}}

二、快速开始-授权

a. 在SpringBoot的启动类上加一个注解:

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
// 启用 spring seucrity 的授权功能
@EnableMethodSecurity(prePostEnabled = true)
@MapperScan("com.cyw.springsecuritydemo.mapper")
@SpringBootApplication
public class SpringSecurityDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSecurityDemoApplication.class, args);
}
}

b. 在api接口上设置接口的访问权限:

import com.cyw.springsecuritydemo.pojo.Rst;
import com.cyw.springsecuritydemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
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;
/**
* <p>
* 前端控制器
* </p>
*
* @author cyw
* @since 2024-04-16
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
// 假设用户a有'sys:settings:add'权限,用户b没有,则用户a可以访问该接口
@PreAuthorize("hasAnyAuthority('sys:settings:add')")
@GetMapping("/hello")
public Rst user(){
return Rst.ok("/user/hello 被成功访问了");
}
}

本文作者:卤香味泡面

本文链接:https://www.cnblogs.com/cywdder/p/18138964

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   卤香味泡面  阅读(195)  评论(0编辑  收藏  举报
(评论功能已被禁用)
   
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起