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/> |
| </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> |
| |
| <dependency> |
| <groupId>io.jsonwebtoken</groupId> |
| <artifactId>jjwt</artifactId> |
| <version>0.9.1</version> |
| </dependency> |
| |
| |
| <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; |
| public static final String APP_SECRET = "amRiYzpteXNxbDovL2xvY2FsaG9zdDozMzA2L2RiX3NlY3VyaXR5P3VzZVNTTD1mYWxzZSZzZXJ2ZXJUaW1lem9uZT1VVEMmY2hhcmFjdGVyRW5jb2Rpbmc9dXRmOA=="; |
| |
| |
| 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) |
| .signWith(SignatureAlgorithm.HS256, APP_SECRET) |
| .compact(); |
| } |
| |
| |
| 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"); |
| if (jwtToken != null && !jwtToken.isEmpty() && jwtUtil.checkToken(jwtToken)){ |
| try { |
| 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 { |
| |
| |
| 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); |
| |
| |
| |
| |
| ; |
| 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(); |
| |
| pm.collectionVariables.set("jwt", jsonData.data); |
c. 其它接口的请求头加一个token
头:
二、快速开始-授权
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; |
| |
| |
| @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; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| @RestController |
| @RequestMapping("/user") |
| public class UserController { |
| @Autowired |
| private UserService userService; |
| |
| |
| @PreAuthorize("hasAnyAuthority('sys:settings:add')") |
| @GetMapping("/hello") |
| public Rst user(){ |
| return Rst.ok("/user/hello 被成功访问了"); |
| } |
| |
| } |
| |
本文作者:卤香味泡面
本文链接:https://www.cnblogs.com/cywdder/p/18138964
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步