所有类
先建一个springboot项目先~~~
1-controller
helloController用于权限测试
@RestController
public class HelloController {
@GetMapping("/hello")
// @PreAuthorize("hasAuthority('test')")
// @PreAuthorize("hasAuthority('system:dept:list')")
@PreAuthorize("@ex.hasAuthority('system:dept:list')")
public String hello() {
return "Hello World!";
}
}
LoginController登录登出
@RestController
public class LoginController {
@Autowired
private LoginService loginService;
@Autowired
private UserDao userDao;
@PostMapping("/login")
public Result login(@RequestBody SysUser sysUser) {
System.out.println("有人登录");
return loginService.login(sysUser);
}
@GetMapping("/info")
public Result getInfo(String token) {
String userId ;
try {
Claims claims = JwtUtil.parseJWT(token);
userId = claims.getSubject();
} catch (Exception e) {
throw new RuntimeException(e);
}
SysUser sysUser = userDao.selectById(userId);
return Result.success(sysUser);
}
@PostMapping("/logout")
public Result logout() {
return loginService.logout();
}
}
2-实体类
LoginUser(实现了Usedetails)
@NoArgsConstructor
@Data
public class LoginUser implements UserDetails {
private SysUser sysUser;
private List<String> permission;
public LoginUser(SysUser sysUser, List<String> permission) {
this.sysUser = sysUser;
this.permission = permission;
}
//序列化关闭,否则序列化到redis里抛异常,为了安全考虑SimpleGrantedAuthority类型无法存储?
@JSONField(serialize = false)
private List<SimpleGrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 使用GrantedAuthority的实现ctrl+alt+左键查看,
// 如果每次get鉴权都得处理成集合那就太麻烦了,提升为成员变量永存,每次get进行判断即可
if (authorities!=null){
return authorities;
}
authorities = permission.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
return authorities;
}
@Override
public String getPassword() {
return sysUser.getPassword();
}
@Override
public String getUsername() {
return sysUser.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;
}
}
User
@TableName("sys_user")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SysUser implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户名
*/
private String userName;
/**
* 昵称
*/
private String nickName;
/**
* 密码
*/
private String password;
/**
* 用户类型:0代表普通用户,1代表管理员
*/
private String type;
/**
* 账号状态(0正常 1停用)
*/
private String status;
/**
* 邮箱
*/
private String email;
/**
* 手机号
*/
private String phonenumber;
/**
* 用户性别(0男,1女,2未知)
*/
private String sex;
/**
* 头像
*/
private String avatar;
/**
* 创建人的用户id
*/
private Long createBy;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新人
*/
private Long updateBy;
/**
* 更新时间
*/
private LocalDateTime updateTime;
/**
* 删除标志(0代表未删除,1代表已删除)
*/
private Integer delFlag;
}
Menu
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("sys_menu")
public class Menu implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 菜单ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 菜单名称
*/
private String menuName;
/**
* 父菜单ID
*/
private Long parentId;
/**
* 显示顺序
*/
private Integer orderNum;
/**
* 路由地址
*/
private String path;
/**
* 组件路径
*/
private String component;
/**
* 是否为外链(0是 1否)
*/
private Integer isFrame;
/**
* 菜单类型(M目录 C菜单 F按钮)
*/
private String menuType;
/**
* 菜单状态(0显示 1隐藏)
*/
private String visible;
/**
* 菜单状态(0正常 1停用)
*/
private String status;
/**
* 权限标识
*/
private String perms;
/**
* 菜单图标
*/
private String icon;
/**
* 创建者
*/
private Long createBy;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新者
*/
private Long updateBy;
/**
* 更新时间
*/
private LocalDateTime updateTime;
/**
* 备注
*/
private String remark;
private String delFlag;
}
Result
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Result {
public static final Integer CODE_SUCCESS = 20000;
public static final Integer CODE_SYS_ERROR = 500;
public static final Integer CODE_AUTH_ERROR = 401;
private Integer code;
private String msg;
private Object data;
public Result(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public static Result success() {
return new Result(CODE_SUCCESS, "请求成功", null);
}
public static Result success(Object data) {
return new Result(CODE_SUCCESS, "请求成功", data);
}
public static Result success(String msg) {
return new Result(CODE_SUCCESS, msg, null);
}
public static Result success(String msg, Object data) {
return new Result(CODE_SUCCESS, msg, data);
}
public static Result success(Integer code, String msg, Object data) {
return new Result(code, msg, data);
}
public static Result success(Integer code, String msg) {
return new Result(code, msg);
}
public static Result error(String msg) {
return new Result(CODE_SYS_ERROR, msg, null);
}
public static Result error(Integer code, String msg) {
return new Result(code, msg, null);
}
public static Result error() {
return new Result(CODE_SYS_ERROR, "系统错误", null);
}
}
3-Service
loginservice
public interface LoginService {
Result login(SysUser sysUser);
Result logout();
}
loginserviceImpl(加工token和设置redis)
@Service
public class LoginServiceImpl implements LoginService {
@Resource
AuthenticationManager authenticationManager;
@Autowired
RedisCache redisCache;
@Override
public Result login(SysUser sysUser) {
// 用户名和密码封装到验证容器对象里
UsernamePasswordAuthenticationToken upat =
new UsernamePasswordAuthenticationToken(sysUser.getUserName(), sysUser.getPassword());
// 开始验证
Authentication authenticate = authenticationManager.authenticate(upat);
// 如果验证结果也就是返回值 也就是在数据库里查得到,那么就验证通过了
if (Objects.isNull(authenticate)) {
throw new RuntimeException("登陆失败");
}
// 认证通过后根据authenticate获取id,制作token,放入map里,返回map
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userId = loginUser.getSysUser().getId().toString();
String jwt = JwtUtil.createJWT(userId);
System.out.println("生成了token:"+jwt);
System.out.println(loginUser.getAuthorities());
redisCache.setCacheObject("login:" + userId, loginUser);
Map<String, String> map = new HashMap<String, String>();
map.put("token", jwt);
return new Result(20000, "登陆成功", map);
}
@Override
public Result logout() {
// jwt过滤器在请求过来的时候给security容器设置了authentication,这里我们直接拿就行
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
Long userId = loginUser.getSysUser().getId();
String rkey = "login:" + userId;
redisCache.deleteObject(rkey);
System.out.println("注销成功!");
return new Result(20000,"注销成功");
}
}
userDetailsServiceImpl自定义实现类
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserDao userDao;
@Autowired
private MenuDao menuDao;
// @Autowired
// private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
System.out.println(username);
wrapper.eq(SysUser::getUserName, username);
SysUser user = userDao.selectOne(wrapper);
System.out.println(user);
if (Objects.isNull(user)){
throw new RuntimeException("用户名或者密码错误");
}
// List<String> permission = new ArrayList<>(Arrays.asList("test","admin"));
List<String> list = menuDao.selectPermsByUserId(Long.valueOf(user.getId()));
return new LoginUser(user,list);
}
}
4-DAO
menuDAO
@Mapper
public interface MenuDao extends BaseMapper<Menu> {
List<String> selectPermsByUserId(Long userId);
}
userDAO
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
menuDao.xml
在resource下创建mapper文件夹然后起名为MenuDao.xml
当然你也可以对着mapper类alt + 回车 生成xml文件,前提是你得安装mybatisX插件
<?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.example.mapper.MenuMapper">
<select id="selectPermsByUserId" resultType="java.lang.String">
SELECT DISTINCT m.perms
FROM sys_user_role ur
LEFT JOIN `sys_role` r ON ur.`role_id` = r.`id`
LEFT JOIN `sys_role_menu` rm ON ur.`role_id`=rm.`role_id`
LEFT JOIN `sys_menu` m ON m.`id`=rm.`menu_id`
WHERE user_id=#{userId}
AND r.`status`=0
AND m.`status`=0
</select>
</mapper>
5-配置类
securityConfig
@Configuration // 配置类
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Autowired
AuthenticationConfiguration authenticationConfiguration;// 获取AuthenticationManager
@Autowired
AccessDeniedHandlerImpl accessDeniedHandler;
@Autowired
AuthenticationEntryPointImpl authenticationEntryPoint;
@Autowired
JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.
// 关闭csrf
csrf().disable()
// 不通过session获取securityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 登录接口放行
.antMatchers("/login").anonymous()
// 其余全部鉴权认证
.anyRequest().authenticated();
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
http.cors();
}
}
redisconfig
@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 serializer = new FastJsonRedisSerializer(Object.class);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
// String类型 value序列器
template.setValueSerializer(serializer);
// Hash的key也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
// Hash类型 value序列器
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
corsConfig
@Configuration
public class CorsConfig {
// 当前跨域请求最大有效时长。这里默认1天
private static final long MAX_AGE = 24 * 60 * 60;
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*"); // 1 设置访问源地址
corsConfiguration.addAllowedHeader("*"); // 2 设置访问源请求头 GET PUT DELETE POST
corsConfiguration.addAllowedMethod("*"); // 3 设置访问源请求方法
corsConfiguration.setMaxAge(MAX_AGE);
source.registerCorsConfiguration("/**", corsConfiguration); // 4 对接口配置跨域设置
return new CorsFilter(source);
}
}
jwt过滤器filter(放行login接口,获取redis的loginuser信息包含权限集合然后security过滤器链鉴权)
权限,认证处理类
Userdao ,menudao,多表查询权限集合
jwtutil,rediscache,webutils,fastjsonRedisSerializer
6-handler处理器
鉴权失败处理器AccessDeniedHandlerImpl
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
Result result = Result.error(HttpStatus.FORBIDDEN.value(), "无权限访问");
String jsonString = JSON.toJSONString(result);
// web响应渲染,指定了响应状态码,json格式,编码utf,getWriter写内容
WebUtils.renderString(response, jsonString);
}
}
认证处理器AuthenticationEntryPointImpl
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
Result result = Result.error(HttpStatus.UNAUTHORIZED.value(),"认证失败,请重新登录");
String jsonString = JSON.toJSONString(result);
// web响应渲染,指定了响应状态码,json格式,编码utf,getWriter写内容
WebUtils.renderString(response, jsonString);
}
}
7-filter过滤器
JWT过滤器
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
RedisCache redisCache;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("token");
if (StringUtils.isEmpty(token)) {
filterChain.doFilter(request, response);
// 如果 token 为空,则直接放行请求,让后续的过滤器链继续处理请求。这样做的原因是:
// 登录接口给security认证
// 访问控制: 在某些场景下,并不是所有的请求都需要经过身份验证或授权。
// 比如一些静态资源、登录接口等,可以允许任何用户访问。对于这些不需要验证的请求,
// 直接放行可以提高系统的灵活性和性能。
// 过滤器链执行顺序: 过滤器链是一系列过滤器按照特定顺序执行的机制。
// 如果不在当前过滤器中直接返回,后续的过滤器链会再次进入当前过滤器,造成重复执行。
// 这可能会导致一些不必要的性能开销或逻辑错误。
//没有token放行 此时的SecurityContextHolder没有用户信息 会被后面的过滤器拦截
return ;
}
// 根据token获取userid
String userId;
try {
Claims claims = JwtUtil.parseJWT(token);
userId = claims.getSubject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("token非法");
}
// 制作redisKey
String rkey = "login:" + userId;
// 查询到用户信息
// 由于getObject封装使用了泛型,不用强转
LoginUser loginUser = redisCache.getCacheObject(rkey);
if (Objects.isNull(loginUser)){
//有token但是查不到,说明token在redis里过期了
throw new RuntimeException("您的登录状态已过期,请重新登录");
}
//存入security容器,以authenticate类型,已认证状态设置为null先
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken
(loginUser, null, loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
// 放行
filterChain.doFilter(request, response);
}
}
8-utils工具类
Jwt
@Component
public class JwtUtil {
// 有效期为
public static final Long JWT_TTL = 60 * 60 * 1000L;// 60 * 60 *1000 一个小时
// 设置秘钥明文
public static final String JWT_KEY = "sangeng";
public static String getUUID() {
String token = UUID.randomUUID().toString().replaceAll("-", "");
return token;
}
/**
* 生成jtw
*
* @param subject token中要存放的数据(json格式)
* @return
*/
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
return builder.compact();
}
/**
* 生成jtw
*
* @param subject token中要存放的数据(json格式)
* @param ttlMillis token超时时间
* @return
*/
public static String createJWT(String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis,
String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if (ttlMillis == null) {
ttlMillis = JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid) // 唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer("sg") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey) // 使用HS256对称加密算法签名, 第二个参数为秘钥
.setExpiration(expDate);
}
/**
* 创建token
*
* @param id
* @param subject
* @param ttlMillis
* @return
*/
public static String createJWT(String id, String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
return builder.compact();
}
public static void main(String[] args) throws Exception {
// System.out.println("制作jwt中...");
// String jwt = createJWT("123");
// System.out.println(jwt);
// System.out.println("解析jwt中...");
// String token = jwt;
String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIyNzhiZjQzNmJkMjM0YzQ2ODgyN2QxYmVhYmQ2NWNmZSIsInN1YiI6IjEiLCJpc3MiOiJzZyIsImlhdCI6MTcyMjg1NTc4NiwiZXhwIjoxNzIyODU5Mzg2fQ.zq8dsvK1ZDTXK-MlHpFg0zvgxrsVpLpzwpOK50OcNLc";
Claims claims = parseJWT(token);
System.out.println(claims.getSubject());
}
/**
* 生成加密后的秘钥 secretKey
*
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length,
"AES");
return key;
}
/**
* 解析
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}
RedisCache
@Component
public class RedisCache {
@Autowired
public RedisTemplate redisTemplate;
public void incrementMapValueCache(String key, String hkey, int step) {
redisTemplate.opsForHash().increment(key, hkey, step);
}
/**
* 缓存基本的对象,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);
}
}
WebUtil(用于认证鉴权处理的response渲染设置)
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
Result result = Result.error(HttpStatus.FORBIDDEN.value(), "无权限访问");
String jsonString = JSON.toJSONString(result);
// web响应渲染,指定了响应状态码,json格式,编码utf,getWriter写内容
WebUtils.renderString(response, jsonString);
}
}
FastJsonRedisSerializer
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);
}
}
9-自定义鉴权表达式
@Component("ex")
public class ZwwExpressionRoot {
public boolean hasAuthority(String authority){
//获取当前用户权限
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser principal = (LoginUser) authentication.getPrincipal();
//前面在自定义认证实现类已经设置了permission
List<String> permission = principal.getPermission();
System.out.println(permission.contains(authority));
return permission.contains(authority);
}
}
application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/sg_security?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
data:
redis:
host: localhost
port: 6379
server:
port: 8880
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
相关依赖
<dependencies>
<!-- Spring Boot 安全功能的starter包,用于web应用的安全控制 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Boot Web功能的starter包,提供web应用的基本功能 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Lombok,提供简单的代码生成工具,减少样板代码,设置为可选依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Spring Boot的测试starter包,用于单元测试和集成测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Security的测试包,用于安全测试 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Redis的starter包,用于集成Redis作为缓存或持久化方案 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- FastJSON,一个Java语言编写的高性能功能完备的JSON库 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.33</version>
</dependency>
<!-- JWT(JSON Web Token)的库,用于生成和解析JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<!-- JAXB API,用于XML和Java对象之间的绑定 -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<!--mybatisplus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.3</version>
</dependency>
<!-- Spring Boot的测试starter包,重复项,可能用于不同目的 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
sql文件
那串乱码密码是经过Bcrypt加密后的,明文为123,你也可以到测试类里生成自己想要的
/*
Navicat Premium Data Transfer
Source Server : localhost_3306_1
Source Server Type : MySQL
Source Server Version : 50744 (5.7.44-log)
Source Host : localhost:3306
Source Schema : sg_security
Target Server Type : MySQL
Target Server Version : 50744 (5.7.44-log)
File Encoding : 65001
Date: 07/08/2024 16:48:06
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
`menu_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '菜单名称',
`parent_id` bigint(20) NULL DEFAULT 0 COMMENT '父菜单ID',
`order_num` int(11) NULL DEFAULT 0 COMMENT '显示顺序',
`path` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '路由地址',
`component` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '组件路径',
`is_frame` int(11) NULL DEFAULT 1 COMMENT '是否为外链(0是 1否)',
`menu_type` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '菜单类型(M目录 C菜单 F按钮)',
`visible` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)',
`status` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0正常 1停用)',
`perms` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限标识',
`icon` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '#' COMMENT '菜单图标',
`create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者',
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '备注',
`del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2034 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '菜单权限表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_menu
-- ----------------------------
INSERT INTO `sys_menu` VALUES (1, '部门管理', 0, 0, 'dept', 'system/dept/index', 1, '', '0', '0', 'system:dept:list1', '#', NULL, NULL, NULL, NULL, '', '0');
INSERT INTO `sys_menu` VALUES (2, '测试', 0, 0, 'test', 'system/test/index', 1, '', '0', '0', 'system:test:list', '#', NULL, NULL, NULL, NULL, '', '0');
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`role_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色名称',
`role_key` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色权限字符串',
`role_sort` int(11) NOT NULL COMMENT '显示顺序',
`status` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色状态(0正常 1停用)',
`del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
`create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者',
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色信息表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, 'CEO', 'ceo', 0, '0', '0', NULL, NULL, NULL, NULL, NULL);
INSERT INTO `sys_role` VALUES (2, 'Coder', 'coder', 0, '0', '0', NULL, NULL, NULL, NULL, NULL);
-- ----------------------------
-- Table structure for sys_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu` (
`role_id` bigint(20) NOT NULL COMMENT '角色ID',
`menu_id` bigint(20) NOT NULL COMMENT '菜单ID',
PRIMARY KEY (`role_id`, `menu_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色和菜单关联表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_role_menu
-- ----------------------------
INSERT INTO `sys_role_menu` VALUES (1, 1);
INSERT INTO `sys_role_menu` VALUES (1, 2);
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT 'NULL' COMMENT '用户名',
`nick_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT 'NULL' COMMENT '昵称',
`password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT 'NULL' COMMENT '密码',
`type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT '0' COMMENT '用户类型:0代表普通用户,1代表管理员',
`status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT '0' COMMENT '账号状态(0正常 1停用)',
`email` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '邮箱',
`phonenumber` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '手机号',
`sex` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '用户性别(0男,1女,2未知)',
`avatar` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '头像',
`create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建人的用户id',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新人',
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
`del_flag` int(11) NULL DEFAULT 0 COMMENT '删除标志(0代表未删除,1代表已删除)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 14787164048663 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '用户表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, '123', '123', '$2a$10$XPrtEs74Qw.MK9JUuazRj.omKpRpp7Ir9QxrQGE/0ptJH.ZHv.1km', '0', '0', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0);
-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`role_id` bigint(20) NOT NULL COMMENT '角色ID',
PRIMARY KEY (`user_id`, `role_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户和角色关联表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1);
SET FOREIGN_KEY_CHECKS = 1;
测试类
结构必须和java里的启动类包结构结果一致,例如启动类在com.example那么该SecurityyyTokenDemoApplicationTests类就得放在
@SpringBootTest
class SecurityyyTokenDemoApplicationTests {
@Resource
private UserDao userDao;
@Test
void test() {
List<User> users = userDao.selectList(null);
System.out.println(users);
}
@Test
void createPwd() {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String encode = bCryptPasswordEncoder.encode("123");
System.out.println(encode);
}
@Test
void checkPwd() {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
boolean matches = bCryptPasswordEncoder.matches("123", "$2a$10$XPrtEs74Qw.MK9JUuazRj.omKpRpp7Ir9QxrQGE/0ptJH.ZHv.1km");
System.out.println(matches);
}
@Autowired
private PasswordEncoder passwordEncoder;
@Test
void checkPwd2() {
boolean matches = passwordEncoder.matches("123", "$2a$10$XPrtEs74Qw.MK9JUuazRj.omKpRpp7Ir9QxrQGE/0ptJH.ZHv.1km");
System.out.println(matches);
}
@Autowired
MenuDao menuDao;
@Test
void testPerms() {
List<String> menus = menuDao.selectPermsByUserId(1L);
System.out.println(menus);
}
}