用JDK8用了差不多十年了,也该更新一下了,毕竟SpringBoot3系列已经不再支持JDK8了,所以本次的整合是基于JDK17的。
SpringBoot3.1.2整合Security时,Security的默认版本为6.1.2。
废话不多说,直接上代码。
(response.write封装我不贴了,这种基础代码自己解决就好)
依赖:pom.xml:
<!--spring security 启动器--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>3.1.2</version> </dependency> <!-- https://mvnrepository.com/artifact/javax.xml.bind/jaxb-api --> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.1</version> </dependency> <!--jwt--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency> <!-- mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.0</version> </dependency> <!-- mysql --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <!--redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
配置文件application.yml:
server: port: 8080 spring: # 数据源配置 datasource: url: jdbc:mysql://localhost:3306/hkbea-gbt?useUnicode=true&characterEncoding=UTF-8 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver # mybatis配置 mybatis: # 配置SQL映射文件路径 mapper-locations: classpath:/mapper/*.xml # 实体类别名包扫描(请更换成实际的) type-aliases-package: **.**.**.** # 驼峰命名 configuration: map-underscore-to-camel-case: true
token异常处理类:AuthenticationEntryPointImpl.java
import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; import java.io.IOException; /** * 认证失败 */ @Component public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { response.setContentType("application/json;charset=UTF-8"); response.getWriter().println("无效登录信息"); } }
permission异常处理类:AccessDeniedHandlerImpl.java
import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.stereotype.Component; import java.io.IOException; @Component public class AccessDeniedHandlerImpl implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException { response.setContentType("application/json;charset=UTF-8"); response.getWriter().println("你无权进行本操作"); } }
核心配置类:SecurityConfig.java
import org.springframework.beans.factory.annotation.Autowired; 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.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; /** * SpringSecurity 核心配置类 * prePostEnabled = true 开启注解权限认证功能 */ @Configuration @EnableWebSecurity @EnableMethodSecurity public class SecurityConfig { @Autowired private AuthenticationConfiguration authenticationConfiguration; //认证过滤器 @Autowired private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; // 认证失败处理器 @Autowired private AuthenticationEntryPointImpl authenticationEntryPoint; // 授权失败处理器 @Autowired private AccessDeniedHandlerImpl accessDeniedHandler; /** * 认证配置 * anonymous():匿名访问:未登录可以访问,已登录不能访问 * permitAll():有没有认证都能访问:登录或未登录都能访问 * denyAll(): 拒绝 * authenticated():认证之后才能访问 * hasAuthority():包含权限 */ @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http // 允许跨域(也可以不允许,一般前后端分离项目要关闭此功能) .cors() //关闭csrf:为了防止通过伪造用户请求来访问受信用站点的非法请求访问 .and() .csrf().disable(); http .anonymous().disable() //禁用anonymous用户(影响Authentication.getPrincipal()的返回):disable=>匿名用户的authentication为null // 禁用session (前后端分离项目,不通过Session获取SecurityContext) .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) //登录配置 //此处全部注释,在本案例中采用注解进行权限拦截 //.and() // 添加token过滤器 //.apply(permitAllSecurityConfig) //.and() // 配置路径是否需要认证 //.authorizeHttpRequests() //配置放行资源(注意: 放行资源必须放在所有认证请求之前!) //.requestMatchers("/api/v1/auth/login").permitAll() //.requestMatchers("/api/v1/auth/register").permitAll() //.requestMatchers("/api/v1/on1on/**").permitAll() //.requestMatchers("/api/v1/admin/**").hasAuthority("admin") // 对于admin接口 需要权限admin // 除上面外的所有请求全部需要鉴权认证 //.anyRequest().authenticated() //代表所有请求,必须认证之后才能访问 //.anyRequest().permitAll() .and() .authenticationManager(authenticationManager(authenticationConfiguration)) .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() //此处为添加jwt过滤 .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class) ; // 配置异常处理器 http.exceptionHandling() // 认证失败 .authenticationEntryPoint(authenticationEntryPoint) // 授权失败 .accessDeniedHandler(accessDeniedHandler); http.headers().frameOptions().disable(); return http.build(); } /** * 注入AuthenticationManager 进行用户认证(基于用户名和密码或使用用户名和密码进行身份验证) * @param authenticationConfiguration * @return * @throws Exception */ @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } /** * 提供密码机密处理机制: * 将BCryptPasswordEncoder对象注入到spring容器中,更换掉原来的 PasswordEncoder加密方式 * 原PasswordEncoder密码格式为:{id}password。它会根据id去判断密码的加密方式。 * 如果没替换原来的加密方式,数据库中想用明文密码做测试,将密码字段改为{noop}123456这样的格式 */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
RBAC中的Account.java:
import lombok.Data; import java.io.Serializable; @Data public class Account implements Serializable { private String id; private String username; private String password; private String nickname; }
RBAC中的AccountDao:AccountDao.java
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; @Mapper public interface AccountDao { /** * 获取用户信息 */ @Select("select * from account where username = #{username}") Account getUserInfo(String username); }
RBAC中的AccountService接口:AccountService.java
public interface AccountService { /** * 根据用户名获取用户信息 */ Account getUserInfo(String username); }
RBAC中的AccountService接口实现类:AccountServiceImpl.java
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; /** * 根据用户名获取用户信息 */ @Override public Account getUserInfo(String username) { return accountDao.getUserInfo(username); } }
RBAC中的权限查询类:AuthDao.java
import org.apache.ibatis.annotations.Mapper; import java.util.List; import java.util.Map; @Mapper public interface AuthDao { List<Map<String,Object>> getRolesAndPermissionsByAccountId(String id); }
RBAC中的权限查询Mapper:AuthMapper.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.hkbea.gbt.dao.AuthDao"> <!--通过手机号查找用户权限信息--> <select id="getRolesAndPermissionsByAccountId" resultType="java.util.HashMap" parameterType="String"> SELECT role.name AS role, permission.name AS permission FROM role, permission, account_role, role_permission WHERE account_role.account_id=#{id} AND account_role.role_id=role.id AND role_permission.role_id=role.id AND role_permission.permission_id=permission.id </select> </mapper>
SpringSecurity核心的用户类:LoginUser.java
import com.alibaba.fastjson2.annotation.JSONField; import lombok.Data; import lombok.NoArgsConstructor; import lombok.extern.log4j.Log4j2; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; /** * 重写UserDetails返回的用户信息 * SpringSecurity返回的用户信息实体类 */ @Log4j2 @Data @NoArgsConstructor public class LoginUser implements UserDetails { private Account account; //用户信息 private List<Map<String,Object>> rolesAndPermissions; //角色信息 private List<String> roles; //角色信息 private List<String> permissions; //权限信息 public LoginUser(Account account, List<Map<String,Object>> rolesAndPermissions) { this.account = account; this.roles = new ArrayList<>(); this.permissions = new ArrayList<>(); for (Map<String,Object> map : rolesAndPermissions){ log.info(map); log.info("role : {}",map.get("role")); this.roles.add((String)map.get("role")); log.info("permission : {}",map.get("permission")); this.permissions.add((String)map.get("permission")); } } @JSONField(serialize = false) //fastjson注解,表示此属性不会被序列化,因为SimpleGrantedAuthority这个类型不能在redis中序列化 private List<SimpleGrantedAuthority> authorities; /** * 获取权限信息 */ @Override public Collection<? extends GrantedAuthority> getAuthorities() { // 权限为空的时候才往遍历,不为空直接返回 if (authorities != null) { return authorities; } //把permissions中String类型的权限信息封装成SimpleGrantedAuthority对象 authorities = new ArrayList<>(); for (String permission : permissions) { SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permission); authorities.add(authority); } return authorities; } /** * 获取密码 */ @Override public String getPassword() { return account.getPassword(); } /** * 获取用户名 */ @Override public String getUsername() { return account.getUsername(); } /** * 判断是否过期(账户是否未过期,过期无法验证) */ @Override public boolean isAccountNonExpired() { return true; } /** * 是否锁定(指定用户是否解锁,锁定的用户无法进行身份验证) */ @Override public boolean isAccountNonLocked() { return true; } /** * 是否没有超时(指示是否已过期的用户的凭据(密码),过期的凭据防止认证) */ @Override public boolean isCredentialsNonExpired() { return true; } /** * 是否可用(用户是否被启用或禁用。禁用的用户无法进行身份验证。) */ @Override public boolean isEnabled() { return true; } }
Redis使用FastJson序列化的相关类:FastJsonRedisSerializer.java
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; import com.alibaba.fastjson.serializer.SerializerFeature; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.type.TypeFactory; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationException; import java.nio.charset.Charset; /** * Redis使用FastJson序列化 */ public class FastJsonRedisSerializer<T> implements RedisSerializer<T> { public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); private Class<T> clazz; static { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); } public FastJsonRedisSerializer(Class<T> clazz) { super(); this.clazz = clazz; } @Override public byte[] serialize(T t) throws SerializationException { if (t == null) { return new byte[0]; } return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET); } @Override public T deserialize(byte[] bytes) throws SerializationException { if (bytes == null || bytes.length <= 0) { return null; } String str = new String(bytes, DEFAULT_CHARSET); return JSON.parseObject(str, clazz); } protected JavaType getJavaType(Class<?> clazz) { return TypeFactory.defaultInstance().constructType(clazz); } }
Redis配置类:RedisConfig.java
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.StringRedisSerializer; /** * redis配置类 * 避免存入redis中的key看上去乱码的现象 */ @Configuration public class RedisConfig { @Bean @SuppressWarnings(value = {"unchecked", "rawtypes"}) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); //使用FastJsonRedisSerializer来序列化和反序列化redis的value值 FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class); // 使用StringRedisSerializer来序列化和反序列化redis的key值 template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(serializer); // Hash的key也采用StringRedisSerializer的序列化方式 template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(serializer); template.afterPropertiesSet(); return template; } }
Redis工具类:RedisCache.java
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.BoundSetOperations; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Component; import java.util.*; import java.util.concurrent.TimeUnit; /** * redis工具类 */ @Component public class RedisCache { @Autowired public RedisTemplate redisTemplate; /** * 缓存基本的对象,Integer、String、实体类等 * * @param key 缓存的键值 * @param value 缓存的值 */ public <T> void setCacheObject(final String key, final T value) { redisTemplate.opsForValue().set(key, value); } /** * 缓存基本的对象,Integer、String、实体类等 * * @param key 缓存的键值 * @param value 缓存的值 * @param timeout 时间 * @param timeUnit 时间颗粒度 */ public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) { redisTemplate.opsForValue().set(key, value, timeout, timeUnit); } /** * 设置有效时间 * * @param key Redis键 * @param timeout 超时时间 * @return true=设置成功;false=设置失败 */ public boolean expire(final String key, final long timeout) { return expire(key, timeout, TimeUnit.SECONDS); } /** * 设置有效时间 * * @param key Redis键 * @param timeout 超时时间 * @param unit 时间单位 * @return true=设置成功;false=设置失败 */ public boolean expire(final String key, final long timeout, final TimeUnit unit) { return redisTemplate.expire(key, timeout, unit); } /** * 获得缓存的基本对象。 * * @param key 缓存键值 * @return 缓存键值对应的数据 */ public <T> T getCacheObject(final String key) { ValueOperations<String, T> operation = redisTemplate.opsForValue(); return operation.get(key); } /** * 删除单个对象 * * @param key */ public boolean deleteObject(final String key) { return redisTemplate.delete(key); } /** * 删除集合对象 * * @param collection 多个对象 * @return */ public long deleteObject(final Collection collection) { return redisTemplate.delete(collection); } /** * 缓存List数据 * * @param key 缓存的键值 * @param dataList 待缓存的List数据 * @return 缓存的对象 */ public <T> long setCacheList(final String key, final List<T> dataList) { Long count = redisTemplate.opsForList().rightPushAll(key, dataList); return count == null ? 0 : count; } /** * 获得缓存的list对象 * * @param key 缓存的键值 * @return 缓存键值对应的数据 */ public <T> List<T> getCacheList(final String key) { return redisTemplate.opsForList().range(key, 0, -1); } /** * 缓存Set * * @param key 缓存键值 * @param dataSet 缓存的数据 * @return 缓存数据的对象 */ public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) { BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key); Iterator<T> it = dataSet.iterator(); while (it.hasNext()) { setOperation.add(it.next()); } return setOperation; } /** * 获得缓存的set * * @param key * @return */ public <T> Set<T> getCacheSet(final String key) { return redisTemplate.opsForSet().members(key); } /** * 缓存Map * * @param key * @param dataMap */ public <T> void setCacheMap(final String key, final Map<String, T> dataMap) { if (dataMap != null) { redisTemplate.opsForHash().putAll(key, dataMap); } } /** * 获得缓存的Map * * @param key * @return */ public <T> Map<String, T> getCacheMap(final String key) { return redisTemplate.opsForHash().entries(key); } /** * 往Hash中存入数据 * * @param key Redis键 * @param hKey Hash键 * @param value 值 */ public <T> void setCacheMapValue(final String key, final String hKey, final T value) { redisTemplate.opsForHash().put(key, hKey, value); } /** * 获取Hash中的数据 * * @param key Redis键 * @param hKey Hash键 * @return Hash中的对象 */ public <T> T getCacheMapValue(final String key, final String hKey) { HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash(); return opsForHash.get(key, hKey); } /** * 删除Hash中的数据 * * @param key * @param hkey */ public void delCacheMapValue(final String key, final String hkey) { HashOperations hashOperations = redisTemplate.opsForHash(); hashOperations.delete(key, hkey); } /** * 获取多个Hash中的数据 * * @param key Redis键 * @param hKeys Hash键集合 * @return Hash对象集合 */ public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) { return redisTemplate.opsForHash().multiGet(key, hKeys); } /** * 获得缓存的基本对象列表 * * @param pattern 字符串前缀 * @return 对象列表 */ public Collection<String> keys(final String pattern) { return redisTemplate.keys(pattern); } }
JWT工具类:JwtTokenUtil.java
import io.jsonwebtoken.*; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; import java.util.Date; import java.util.HashMap; import java.util.Map; @Configuration @Component @Slf4j public class JwtTokenUtil { /** * token的头key */ public static final String AUTH_HEADER_KEY = "Authorization"; /** * token前缀 */ public static final String TOKEN_PREFIX = "Bearer "; /** * token 过期时间 30分钟 */ public static final long EXPIRATION = 1000 * 60 * 30; /** * 加密的key(自己生成的任意值) */ public static final String APP_SECRET_KEY = "secret"; /** * 生成token * @return token字符串 */ public static String createToken(Account account) { Map<String, Object> claims = new HashMap<>(); claims.put("id", account.getId()); claims.put("username", account.getUsername()); claims.put("nickname", account.getNickname()); String token = Jwts .builder() .setId(account.getId()) .setClaims(claims) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION)) .signWith(SignatureAlgorithm.HS256, APP_SECRET_KEY) .compact(); return TOKEN_PREFIX +token; } /** * 解析jwt * @return 解析后的JWT Claims */ public static Claims parseJwt(String token) { try { return Jwts.parser().setSigningKey(APP_SECRET_KEY).parseClaimsJws(token.replace(TOKEN_PREFIX, "")).getBody(); } catch (Exception e) { log.error("token exception : ", e); } return null; } /** * 获取当前登录用户的ID * * @param token * @return */ public static String getAccountId(String token) { Claims claims = parseJwt(token); return (String)claims.get("id"); } /** * 获取当前登录用户用户名 * * @param token * @return */ public static String getUsername(String token) { Claims claims = parseJwt(token); return (String)claims.get("username"); } /** * 检查token是否过期 * * @param token token * @return boolean */ public static boolean isExpiration(String token) { Claims claims = parseJwt(token); return claims.getExpiration().before(new Date()); } public boolean validateToken(String token) { try { Jwts.parser().setSigningKey(APP_SECRET_KEY).parseClaimsJws(token); return true; } catch (SignatureException e) { log.error("Invalid JWT signature: {}", e.getMessage()); } catch (MalformedJwtException e) { log.error("Invalid JWT token: {}", e.getMessage()); } catch (ExpiredJwtException e) { log.error("JWT token is expired: {}", e.getMessage()); } catch (UnsupportedJwtException e) { log.error("JWT token is unsupported: {}", e.getMessage()); } catch (IllegalArgumentException e) { log.error("JWT claims string is empty: {}", e.getMessage()); } return false; } }
JWT权限拦截过滤器:JwtAuthenticationTokenFilter.java
import com.alibaba.fastjson.JSONObject; import io.jsonwebtoken.Claims; import jakarta.annotation.Resource; import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.log4j.Log4j2; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import java.util.Objects; /** * token认证过滤器 * 作用:解析请求头中的token。并验证合法性 * 继承 OncePerRequestFilter 保证请求经过过滤器一次 */ @Log4j2 @Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Resource private RedisCache redisCache; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain){ String token = request.getHeader(JwtTokenUtil.AUTH_HEADER_KEY); if (StringUtils.hasText(token)) { Claims claims = JwtTokenUtil.parseJwt(token); if(claims!=null){ String accountId = (String)claims.get("id"); String redisKey = "login:" + accountId; // 先转成JSON对象 JSONObject jsonObject = redisCache.getCacheObject(redisKey); // JSON对象转换成Java对象 LoginUser loginUser = jsonObject.toJavaObject(LoginUser.class); // redis中用户不存在 if (Objects.isNull(loginUser)) { throw new RuntimeException("redis中用户不存在!"); } // 将Authentication对象(用户信息、已认证状态、权限信息)存入 SecurityContextHolder UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } } log.info("authentication :{}",SecurityContextHolder.getContext().getAuthentication()); //放行 try { filterChain.doFilter(request, response); //放行,因为后面的会抛出相应的异常 }catch (Exception e){ e.printStackTrace(); } } }
鉴权Controller类:AuthApiControllerV1.java
import com.alibaba.fastjson2.JSONObject; import com.hkbea.gbt.controller.api.v1.util.ApiControllerExceptionV1; import com.hkbea.gbt.controller.api.v1.util.ResultVo; import com.hkbea.gbt.model.Account; import com.hkbea.gbt.on1on.On1onApiUtil; import com.hkbea.gbt.security.core.LoginUser; import com.hkbea.gbt.security.service.LoginService; import com.hkbea.gbt.service.AccountService; import jakarta.annotation.security.PermitAll; import lombok.extern.log4j.Log4j2; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import java.util.UUID; @Log4j2 @RestController @RequestMapping("/api/v1/auth") public class AuthApiControllerV1 { @Autowired private LoginService loginService; @Autowired private AccountService accountService; @PostMapping("login") @PermitAll public ResultVo login(@RequestBody JSONObject body) throws ApiControllerExceptionV1 { String username = body.getString("username"); String password = body.getString("password"); if(username==null||(username=username.trim()).equals("")){ throw new ApiControllerExceptionV1("请输入正确的email账号"); } if(password==null||(password=password.trim()).equals("")){ throw new ApiControllerExceptionV1("请输入password"); } String jwt = loginService.login(username,password); if(StringUtils.isEmpty(jwt)){ throw new ApiControllerExceptionV1("账号或密码错误"); } return new ResultVo(ResultVo.SUCCESS,"登录成功",jwt); } @GetMapping("info") @PreAuthorize("authenticated") public ResultVo info(Authentication authentication) throws ApiControllerExceptionV1 { LoginUser loginUser = (LoginUser) authentication.getPrincipal(); Account account = loginUser.getAccount(); JSONObject out = new JSONObject(); out.put("id",account.getId()); out.put("username",account.getUsername()); out.put("on1on_id",account.getOn1onId()); return new ResultVo(out); } /** * 退出登录 */ @RequestMapping(value="logout", method = {RequestMethod.DELETE}) @PreAuthorize("authenticated") public ResultVo logout() throws ApiControllerExceptionV1{ if(loginService.logout()){ return new ResultVo(ResultVo.SUCCESS,"注销成功"); } throw new ApiControllerExceptionV1("退出登录异常"); } }
测试Controller:TestApiControllerV1.java
import com.alibaba.fastjson2.JSONObject; import jakarta.annotation.security.PermitAll; import lombok.extern.log4j.Log4j2; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; @Log4j2 @RestController @RequestMapping("/api/v1/test") public class TestApiControllerV1 { @GetMapping("") @PermitAll public ResultVo test(){ log.info("test"); JSONObject out = new JSONObject(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if(authentication!=null){ LoginUser loginUser = (LoginUser) authentication.getPrincipal(); System.out.println("username :"+loginUser.getAccount().getUsername()); System.out.println("凭证 :"+authentication.getCredentials()); System.out.println("权限 :"+authentication.getAuthorities()); out.put("username",loginUser.getAccount().getUsername()); out.put("credentials",authentication.getCredentials()); out.put("authorities",authentication.getAuthorities()); } return new ResultVo(out); } }
MySQL测试数据脚本:
SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for account -- ---------------------------- DROP TABLE IF EXISTS `account`; CREATE TABLE `account` ( `id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of account -- ---------------------------- INSERT INTO `account` VALUES ('5a07d8d95b9a46179d10a67f3765d363', 'admin', '$2a$10$MKzkFJJLuGMWO1eL9YuS9uinjq6H4Kalf9MzDxIFPQ4.fV9IKHNCC', '管理员'); INSERT INTO `account` VALUES ('85a223c1a519463e9e8de3a096818e6a', 'user', '123456', '普通用户'); -- ---------------------------- -- Table structure for account_role -- ---------------------------- DROP TABLE IF EXISTS `account_role`; CREATE TABLE `account_role` ( `id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, `account_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, `role_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of account_role -- ---------------------------- INSERT INTO `account_role` VALUES ('eb014d68525849c29a5998e7133c1db9', '5a07d8d95b9a46179d10a67f3765d363', '28fb6f155b3b49e38a7241893b8d03f2'); -- ---------------------------- -- Table structure for permission -- ---------------------------- DROP TABLE IF EXISTS `permission`; CREATE TABLE `permission` ( `id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of permission -- ---------------------------- INSERT INTO `permission` VALUES ('80ad0fd1296f48a8bdd98ec700947c63', 'admin_console'); -- ---------------------------- -- Table structure for role -- ---------------------------- DROP TABLE IF EXISTS `role`; CREATE TABLE `role` ( `id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of role -- ---------------------------- INSERT INTO `role` VALUES ('28fb6f155b3b49e38a7241893b8d03f2', '管理员'); -- ---------------------------- -- Table structure for role_permission -- ---------------------------- DROP TABLE IF EXISTS `role_permission`; CREATE TABLE `role_permission` ( `id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, `role_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, `permission_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of role_permission -- ---------------------------- INSERT INTO `role_permission` VALUES ('28e1307e38124275b9e2d428adee3e0b', '28fb6f155b3b49e38a7241893b8d03f2', '80ad0fd1296f48a8bdd98ec700947c63'); SET FOREIGN_KEY_CHECKS = 1;
测试结果: