开始之前了解: ras 公钥密钥 解析加密 token,aop实现自定义注解,登录用到一点mybatis plus,这篇文章只有后端代码
源码:https://gitee.com/sun-jianhui/leyou(在dev-sjh分支下)
用到的数据库(数据库有点绕)
管理员表
角色表
权限表
token生成,解析,加密
JwtUtils
public class JwtUtils {
/**
* 私钥加密token
* @param data 需要加密的数据(载荷内容)
* @param expireMinutes 过期时间,单位:分钟
* @param privateKey 私钥
* @return
*/
public static String generateToken(Object data, int expireMinutes, PrivateKey privateKey) throws Exception {
//1 获得jwt构建对象
JwtBuilder jwtBuilder = Jwts.builder();
//2 设置数据
if( data == null ) {
throw new RuntimeException("数据不能为空");
}
BeanInfo beanInfo = Introspector.getBeanInfo(data.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
// 获得属性名
String name = propertyDescriptor.getName();
// 获得属性值
Object value = propertyDescriptor.getReadMethod().invoke(data);
if(value != null) {
jwtBuilder.claim(name,value);
}
}
//3 设置过期时间
jwtBuilder.setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate());
//4 设置加密
jwtBuilder.signWith(SignatureAlgorithm.RS256, privateKey);
//5 构建
return jwtBuilder.compact();
}
/**
* 通过公钥解析token
* @param token 需要解析的数据
* @param publicKey 公钥
* @param beanClass 封装的JavaBean
* @return
* @throws Exception
*/
public static T getObjectFromToken(String token, PublicKey publicKey, Class beanClass) throws Exception {
//1 获得解析后内容
Claims body = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token).getBody();
//2 将内容封装到对象JavaBean
T bean = beanClass.newInstance();
BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
// 获得属性名
String name = propertyDescriptor.getName();
// 通过属性名,获得对应解析的数据
Object value = body.get(name);
if(value != null) {
// 将获得的数据封装到对应的JavaBean中
BeanUtils.setProperty(bean,name,value);
}
}
return bean;
}
}
RasUtils
public class RasUtils {
public static String publicKeyPath="E:\\project\\IdeaProjects\\jwt_demo\\test\\ras.pub";
public static String privateKeyPath="E:\\project\\IdeaProjects\\jwt_demo\\test\\ras.pri";
/**
* 从文件中读取公钥
*
* @param filename 公钥保存路径,相对于classpath
* @return 公钥对象
* @throws Exception
*/
public static PublicKey getPublicKey(String filename) throws Exception {
byte[] bytes = readFile(filename);
return getPublicKey(bytes);
}
/**
* 从文件中读取密钥
*
* @param filename 私钥保存路径,相对于classpath
* @return 私钥对象
* @throws Exception
*/
public static PrivateKey getPrivateKey(String filename) throws Exception {
byte[] bytes = readFile(filename);
return getPrivateKey(bytes);
}
/**
* 获取公钥
*
* @param bytes 公钥的字节形式
* @return
* @throws Exception
*/
public static PublicKey getPublicKey(byte[] bytes) throws Exception {
X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePublic(spec);
}
/**
* 获取密钥
*
* @param bytes 私钥的字节形式
* @return
* @throws Exception
*/
public static PrivateKey getPrivateKey(byte[] bytes) throws Exception {
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePrivate(spec);
}
/**
* 根据密文,生存rsa公钥和私钥,并写入指定文件
*
* @param publicKeyFilename 公钥文件路径
* @param privateKeyFilename 私钥文件路径
* @param secret 生成密钥的密文
* @throws Exception
*/
public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret) throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
SecureRandom secureRandom = new SecureRandom(secret.getBytes());
keyPairGenerator.initialize(1024, secureRandom);
KeyPair keyPair = keyPairGenerator.genKeyPair();
// 获取公钥并写出
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
writeFile(publicKeyFilename, publicKeyBytes);
// 获取私钥并写出
byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
writeFile(privateKeyFilename, privateKeyBytes);
}
private static byte[] readFile(String fileName) throws Exception {
return Files.readAllBytes(new File(fileName).toPath());
}
private static void writeFile(String destPath, byte[] bytes) throws IOException {
File dest = new File(destPath);
//创建父文件夹
if(!dest.getParentFile().exists()){
dest.getParentFile().mkdirs();
}
//创建需要的文件
if (!dest.exists()) {
dest.createNewFile();
}
Files.write(dest.toPath(), bytes);
}
}
登录
@Service
public class AdministratorsManagerServiceImpl extends ServiceImpl implements AdministratorsManagerService {
@Autowired
private AdministratorsManagerMapper administratorsManagerMapper;
@Autowired
private AdministratorsAuthorityMapper authorityMapper;
@Autowired
private RedisTemplate redisTemplate;
@Override
public ResultLogin managerLogin(ManagerLoginVo managerLoginVo) {
ResultLogin result=new ResultLogin();
String token=null;
QueryWrapper wrapper=new QueryWrapper();
wrapper.eq("manager_account_number",managerLoginVo.getManagerAccountNumber());
wrapper.eq("manager_password",managerLoginVo.getManagerPassword());
AdministratorsManager administratorsManager = administratorsManagerMapper.selectOne(wrapper);
//登录成功
if(administratorsManager!=null) {
List listByManagerNumber = authorityMapper.getListByManagerNumber(administratorsManager.getRoleNumber());
//将管理员编号 放入session 并将session为键权限编号为值的数据存入redis。方便权限认证
String managerAccountNumber = administratorsManager.getManagerAccountNumber();
SetOperations zSetOperations = redisTemplate.opsForSet();
zSetOperations.members(managerAccountNumber);
for(String number:listByManagerNumber){
zSetOperations.add(managerAccountNumber,number);
}
//将userLogin 加密生成token传递给前端
result.setCode("200");
try {
UserLogin userLogin=new UserLogin();
userLogin.setUserName(administratorsManager.getManagerName());
userLogin.setManagerAccountNumber(administratorsManager.getManagerAccountNumber());
userLogin.setRoleNumber(administratorsManager.getRoleNumber());
token = JwtUtils.generateToken(userLogin, 60*24*7, RasUtils.getPrivateKey(RasUtils.privateKeyPath));
} catch (Exception e) {
e.printStackTrace();
}
result.setResult(Result.RESULT_FLG.SUCCESS.getValue());
result.setData(administratorsManager);
result.setMsg("登录成功");
result.setToken(token);
}else {
result.setCode("404");
result.setResult(Result.RESULT_FLG.FAIL.getValue());
result.setData(null);
result.setMsg("账号或密码错误");
}
return result;
}
}
UserLogin (用户名 用户编码 角色编码)
@Data
public class UserLogin {
private String userName;
private String managerAccountNumber;
private String roleNumber;
}
拦截器
WebAppConfig 和 TokenIntercepter
@Configurtion
public class WebAppConfig implements WebMvcConfigurer {
@Autowired
private TokenIntercepter tokenIntercepter;
@Override
public void addInterceptors(InterceptorRegistry registry) {
System.err.println("先进的拦截");
registry.addInterceptor(tokenIntercepter).addPathPatterns("/**");
}
}
@Slf4j
@Component
public class TokenIntercepter implements HandlerInterceptor {
//过滤的
private String[] urls={
"/management-personnel/login/managerLogin",
"/management-personnel/login/testHttpRequest",
};
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
String method=request.getMethod();
if(method.equals("OPTIONS")) {
System.out.println("啥东西啊");
return true;
}
// log.info("-----------------进入token拦截器-----------------");
String url=request.getRequestURI();
String token=request.getHeader("token");
//System.err.println(token);
//System.err.println(url);
//System.err.println(method);
for(String item:urls){
if(url.equals(item)){
return true;
}
}
UserLogin objectFromToken=null;
try {
objectFromToken= JwtUtils.getObjectFromToken(token, RasUtils.getPublicKey(publicKeyPath), UserLogin.class);
System.out.println(objectFromToken);
if(objectFromToken==null){
return false;
}
else {
request.setAttribute("managerAccountNumber",objectFromToken.getManagerAccountNumber());
return true;
}
}catch (ExpiredJwtException exception){
log.info("登录超时了");
PrintWriter writer=null;
try {
writer = response.getWriter();
Map map=new HashMap(8);
map.put("code","403.1");
String string = JSON.toJSONString(map);
writer.write(string);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(writer!=null){
writer.close();
}
}
return false;
}catch (Exception exception){
exception.printStackTrace();
return false;
}finally {
}
}
}
自定义注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthorityForRCheck {
String authority();
}
aop实现自定义注解
@Slf4j
@Component
@Aspect
public class AuthorityAnnotation {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private HttpServletRequest request;
@Pointcut("@annotation(com.leyou.annotation.AuthorityForRCheck)")
public void annotationWhere(){
}
@Around("annotationWhere()")
public Object authorityAround(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("进入aop环绕");
if(request != null){
// Do something
String managerAccountNumber = (String)request.getAttribute("managerAccountNumber");
if(managerAccountNumber!=null){
//根据已经登录的管理员编号 获取权限列表
Set members = redisTemplate.opsForSet().members(managerAccountNumber);
//获取注解的所需要的权限
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
Method method = signature.getMethod();
AuthorityForRCheck annotation = method.getAnnotation(AuthorityForRCheck.class);
//比较是否存在
for (Object member : members) {
String authorityNumber=member.toString();
if(authorityNumber.equals("main")){
return joinPoint.proceed();
}
if(annotation.authority().equals(authorityNumber)){
return joinPoint.proceed();
}
}
return R.error();
}else{
return R.error();
}
}else{
return R.error();
}
}
}
controller层的方法
在需要权限验证的方法上加上自定义注解并设置访问权限,权限编号对应数据库