介绍
前一段时间公司要求登录和鉴权使用SpringSecurity,看了很多都感觉不太适合企业开发,于是自己整理了一下,基于@PreAuthorize注解鉴权,大大的方便的权限控制。
设计技术
本项目使用mysql数据库、mybatis-plus、redis缓存、jwt、hutool工具包、fastjson2。
Maven
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- 数据库相关--><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3</version></dependency><!-- lombook--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!-- hutool--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.15</version></dependency><!--redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.6.13</version></dependency><!--security--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!--jwt依赖--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.0</version></dependency><!--JSON解析工具--><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.10</version></dependency>
数据库
需要四张表,如下图:
sys_user(用户表)
sys_role(角色表)
sys_permission(权限表)
sys_user_role(用户角色表)
搭建环境
自己搭建哦!!
配置类
import com.qian.chatgpt.entity.info.SecurityInfo;
import com.qian.chatgpt.handler.AuthenticationHandler;
import com.qian.chatgpt.handler.CustomLogoutHandler;
import com.qian.chatgpt.handler.CustomLogoutSuccessHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;/*** @author Qiansheng* @date 2023/4/23* @description security配置类*/
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启权限注解全局安全认证
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class SecurityConfig extends WebSecurityConfigurerAdapter {private final UserDetailsService userDetailsService;private final AuthenticationHandler authenticationHandler;private final com.qian.chatgpt.filter.AuthenticationTokenFilter AuthenticationTokenFilter;private final SecurityInfo securityInfo;@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService);}@Overrideprotected void configure(HttpSecurity http) throws Exception {String[] anonymouPaths = securityInfo.getPermitAllPaths().toArray(new String[0]);http// CSRF禁用,因为不使用session.csrf().disable()// 认证失败处理类,指定异常处理实现类.exceptionHandling().authenticationEntryPoint(authenticationHandler).and()// 基于token,所以不需要session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()// 过滤请求.authorizeRequests()//设置匿名访问.antMatchers(anonymouPaths).permitAll()//其他全部拦截认证.anyRequest().authenticated().and().logout().addLogoutHandler(new CustomLogoutHandler()).logoutSuccessHandler(new CustomLogoutSuccessHandler());//将认证过滤器添加到security中http.addFilterBefore(AuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);}/*** 设置加密方式** @return*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}
是不是有很多爆红呢?问题比大,我们一个一个来
UserDetailsService:此类是用来从数据库查询用户信息的
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.qian.chatgpt.entity.user.SysPermission;
import com.qian.chatgpt.entity.user.SysUser;
import com.qian.chatgpt.entity.user.SysUserRole;
import com.qian.chatgpt.mapper.SysUserMapper;
import com.qian.chatgpt.service.user.ISysPermissionService;
import com.qian.chatgpt.service.user.ISysUserRoleService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
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.Service;import java.util.List;/*** @author : Qiansheng* @创建时间 : 2023/4/23* @描述信息 : security 对比用户名类*/
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class UserDetailsServiceImpl implements UserDetailsService {private final SysUserMapper sysUserMapper;private final ISysPermissionService ISysPermissionService;private final ISysUserRoleService sysUserRoleService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {SysUser user = sysUserMapper.selectOne(Wrappers.lambdaQuery(SysUser.class).eq(SysUser::getUsername,username));//判断用户名是否存在 如存在就将信息存储if (user == null) {throw new UsernameNotFoundException("用户不存在: " + username);}String permissionList="";//查询权限SysUserRole userRole = sysUserRoleService.getOne(Wrappers.lambdaQuery(SysUserRole.class).eq(SysUserRole::getUserId, user.getId()));List<SysPermission> permissions = ISysPermissionService.list(Wrappers.lambdaQuery(SysPermission.class).eq(SysPermission::getRoleId, userRole.getRoleId()));for (SysPermission permission : permissions) {permissionList=permissionList+permission.getPermissionKey()+",";}if(permissionList.length()!=0){permissionList = permissionList.substring(0,permissionList.length()-1);}User user1 = new User(user.getUsername(), user.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(permissionList));return user1;}
}
AuthenticationHandler:此类是用来处理认证失败的异常处理类
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONUtil;
import com.qian.chatgpt.util.R;
import com.qian.chatgpt.util.ServletUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
/*** @author : Qiansheng* @创建时间:2023/4/23* @描述信息: security 拦截后返回内容*/
@Component
@Slf4j
public class AuthenticationHandler implements AuthenticationEntryPoint, Serializable {private static final long serialVersionUID = -8970718410437077606L;@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {ServletUtil.renderString(HttpStatus.HTTP_FORBIDDEN, response, JSONUtil.toJsonStr(R.fail(HttpStatus.HTTP_FORBIDDEN, "非法请求:" +e.getMessage())));}
}
SecurityInfo: 放行路径信息实体
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;import java.util.Set;/*** @author gqs* @date 2023/5/16 16:49* @description 放行路径信息实体*/
@Component
@ConfigurationProperties("security")
@Data
public class SecurityInfo {/*** 放行路径*/private Set<String> permitAllPaths;}
所有需要放行的路径放在yml配置文件中:
AuthenticationTokenFilter:用户请求认证拦截器
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.qian.chatgpt.util.JwtUtil;
import com.qian.chatgpt.util.RedisCache;
import io.jsonwebtoken.Claims;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** @author gqs* @date 2023/5/16 16:49* @description 用户请求token认证拦截器*/
@Component
public class AuthenticationTokenFilter extends OncePerRequestFilter {@Resourceprivate RedisCache redisCache;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {//获取tokenString token = request.getHeader("token");if (!StringUtils.hasText(token)) {//放行filterChain.doFilter(request, response);return;}//使用token从redis获取jwtString cacheObject = redisCache.getCacheObject(token);//使用jwt获取用户信息并处理Claims claims = null;try {claims = JwtUtil.parseJWT(cacheObject);} catch (Exception e) {e.printStackTrace();}if(claims==null){filterChain.doFilter(request, response);return;}String usersubject = claims.getSubject();JSONObject entries = JSON.parseObject(usersubject);JSONArray authorities = entries.getJSONArray("authorities");String authlist="";for (int i = 0; i < authorities.size(); i++) {JSONObject o = (JSONObject) authorities.get(i);authlist=authlist+o.getString("authority")+",";}if(authlist.length()!=0){authlist = authlist.substring(0,authlist.length()-1);}User user = new User(entries.getString("username"), "", AuthorityUtils.commaSeparatedStringToAuthorityList(authlist));//存入SecurityContextHolderUsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());SecurityContextHolder.getContext().setAuthentication(authenticationToken);//放行filterChain.doFilter(request, response);}
}
好了,配置信息差不多就这些。
登录
登录的service:
public R login(SysUser tUser) {UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(tUser.getUsername(), tUser.getPassword());authenticate = authenticationManagerBean.authenticate(authenticationToken);if (Objects.isNull(authenticate)) {throw new RuntimeException("用户名或密码错误");}//获取用户信息User user = (User) authenticate.getPrincipal();//将用户信息存入jwtlog.info(user.toString());// hotool工具解析json少东西 更换为fastjsonString s1 = JSON.toJSONString(user);String jwt = JwtUtil.createJWT(s1,3600000L);//将jwt存入redisString token = UUID.randomUUID().toString().replaceAll("-","");redisCache.setCacheObject(token, jwt,3600000L);//把token响应给前端HashMap<String, String> map = new HashMap();map.put("token", token);return R.data(map);}
controller
import com.qian.chatgpt.util.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** @author gqs* @作者: Qiansheng* @创建时间:2023/4/23* @描述信息: TODO*/
@Slf4j
@RestController
@RequestMapping("/admin")
@CrossOrigin //关闭跨域验证
@Api(tags = "管理员管理")
public class AdminTestController {@PreAuthorize("hasAnyAuthority('admin:sel')")@ApiOperation(value = "管理员权限测试", notes = "管理员权限测试")@GetMapping("/test")public R test() {return R.success("admin权限测试成功");}
}
注意:@PreAuthorize(“hasAnyAuthority(‘admin:sel’)”) 中的admin:sel是权限