Sfoglia il codice sorgente

fix:shiro登录认证和授权

wangzaijun 7 mesi fa
parent
commit
8fa9d722cb
24 ha cambiato i file con 642 aggiunte e 338 eliminazioni
  1. 7 0
      readme.md
  2. 16 0
      service-base/pom.xml
  3. 41 0
      service-base/src/main/java/com/simuwang/base/components/ShiroLoginAuthAdapter.java
  4. 37 0
      service-base/src/main/java/com/simuwang/base/components/ShiroRsaCredentialsMatcher.java
  5. 40 0
      service-base/src/main/java/com/simuwang/base/config/DaqProperties.java
  6. 61 21
      service-base/src/main/java/com/simuwang/shiro/config/ShiroConfig.java
  7. 0 18
      service-base/src/main/java/com/simuwang/base/pojo/SysUser.java
  8. 0 142
      service-base/src/main/java/com/simuwang/shiro/LinkProperties.java
  9. 0 32
      service-base/src/main/java/com/simuwang/shiro/PropertiesUtil.java
  10. 4 16
      service-base/src/main/java/com/simuwang/shiro/core/ShiroToken.java
  11. 3 0
      service-base/src/main/java/com/simuwang/shiro/core/ShiroUser.java
  12. 3 3
      service-base/src/main/java/com/simuwang/shiro/core/adapter/AuthAdapter.java
  13. 8 7
      service-base/src/main/java/com/simuwang/shiro/core/adapter/SimpleAuthAdapter.java
  14. 7 10
      service-base/src/main/java/com/simuwang/shiro/core/bridge/AuthBridgeService.java
  15. 9 11
      service-base/src/main/java/com/simuwang/shiro/core/impl/ShiroDbRealmImpl.java
  16. 111 0
      service-base/src/main/java/com/simuwang/shiro/core/jwt/JwtFilter.java
  17. 108 0
      service-base/src/main/java/com/simuwang/shiro/core/jwt/JwtUtil.java
  18. 0 5
      service-base/src/main/resources/authc.properties
  19. 67 54
      service-deploy/src/main/java/com/simuwang/deploy/components/ErrorInfoBuilder.java
  20. 1 1
      service-deploy/src/main/java/com/simuwang/deploy/config/GlobalResponseBodyAdvice.java
  21. 13 0
      service-deploy/src/main/resources/application.yml
  22. 68 18
      service-manage/src/main/java/com/simuwang/manage/api/LoginController.java
  23. 16 0
      service-manage/src/main/java/com/simuwang/manage/api/TestController.java
  24. 22 0
      service-manage/src/main/java/com/simuwang/manage/dto/LoginUser.java

+ 7 - 0
readme.md

@@ -28,6 +28,13 @@
 > `service-base`依赖了所有第三方包,包括一个私有包,`service-calc`、`service-daq`和`service-manage`依赖`service-base`模块
 
 
+### 待办事项
+- [x] shiro 基于jwt的登录认证+授权
+- [x] 自定义基于rsa的密码匹配器 `ShiroRsaCredentialsMatcher`
+- [x] 可实现的用户信息适配器 `LoginAuthAdapter`
+- [ ] 用户信息接入数据库数据
+
+
 ### FAQ
 
 - 1. 下列包的作用?

+ 16 - 0
service-base/pom.xml

@@ -98,6 +98,10 @@
         </dependency>
         <dependency>
             <groupId>cn.hutool</groupId>
+            <artifactId>hutool-crypto</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
             <artifactId>hutool-extra</artifactId>
             <version>5.8.31</version>
         </dependency>
@@ -161,6 +165,18 @@
 <!--            <artifactId>spring-web</artifactId>-->
 <!--            <version>6.1.12</version>-->
 <!--        </dependency>-->
+
+        <!-- jjwt依赖包 -->
+        <dependency>
+            <groupId>com.auth0</groupId>
+            <artifactId>java-jwt</artifactId>
+            <version>4.4.0</version>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.12.1</version>
+        </dependency>
     </dependencies>
 
     <repositories>

+ 41 - 0
service-base/src/main/java/com/simuwang/base/components/ShiroLoginAuthAdapter.java

@@ -0,0 +1,41 @@
+package com.simuwang.base.components;
+
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.crypto.asymmetric.KeyType;
+import cn.hutool.crypto.asymmetric.RSA;
+import com.simuwang.base.config.DaqProperties;
+import com.simuwang.shiro.core.ShiroUser;
+import com.simuwang.shiro.core.adapter.LoginAuthAdapter;
+
+import java.util.List;
+
+/**
+ * 自定义的登录认证适配器实现,从数据库获取数据并且bean注入要调整为当前实现
+ */
+public class ShiroLoginAuthAdapter implements LoginAuthAdapter {
+    private final DaqProperties properties;
+
+    public ShiroLoginAuthAdapter(DaqProperties properties) {
+        this.properties = properties;
+    }
+
+    @Override
+    public ShiroUser findUserByUsername(String username) {
+        ShiroUser sysUser = new ShiroUser();
+        sysUser.setUserId("0");
+        sysUser.setUsername("system");
+        String encryptBase64Pwd = new RSA(null, this.properties.getSecurityRsa().getPublicKey()).encryptBase64("123456", KeyType.PublicKey);
+        sysUser.setPassword(encryptBase64Pwd);
+        return sysUser;
+    }
+
+    @Override
+    public List<String> findRoleByUserId(String userId) {
+        return ListUtil.toList("SYSTEM");
+    }
+
+    @Override
+    public List<String> findPermissionsByUserId(String userId) {
+        return ListUtil.toList("RS:ALL", "RS:TEST");
+    }
+}

+ 37 - 0
service-base/src/main/java/com/simuwang/base/components/ShiroRsaCredentialsMatcher.java

@@ -0,0 +1,37 @@
+package com.simuwang.base.components;
+
+import cn.hutool.crypto.asymmetric.KeyType;
+import cn.hutool.crypto.asymmetric.RSA;
+import com.simuwang.base.config.DaqProperties;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
+
+public class ShiroRsaCredentialsMatcher extends SimpleCredentialsMatcher {
+    private final DaqProperties properties;
+
+    public ShiroRsaCredentialsMatcher(DaqProperties properties) {
+        this.properties = properties;
+    }
+
+    @Override
+    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
+        Object credentials = token.getCredentials();
+        String tokenHashedPassword;
+        if (credentials instanceof char[] temp) {
+            tokenHashedPassword = new String(temp);
+        } else {
+            tokenHashedPassword = credentials.toString();
+        }
+        // 前端传递密码解密
+        String encryptedPassword = this.encryptPassword(tokenHashedPassword);
+        // 数据库密码解密
+        String storedEncryptedPassword = this.encryptPassword(info.getCredentials().toString());
+        return encryptedPassword.equals(storedEncryptedPassword);
+    }
+
+    private String encryptPassword(String password) {
+        DaqProperties.SecurityRsa securityRsa = this.properties.getSecurityRsa();
+        return new RSA(securityRsa.getPrivateKey(), null).decryptStr(password, KeyType.PrivateKey);
+    }
+}

+ 40 - 0
service-base/src/main/java/com/simuwang/base/config/DaqProperties.java

@@ -0,0 +1,40 @@
+package com.simuwang.base.config;
+
+import cn.hutool.core.map.MapUtil;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+import java.util.Map;
+
+@Setter
+@Getter
+@Configuration
+@ConfigurationProperties(prefix = "simuwang")
+public class DaqProperties {
+    /**
+     * token 过期时间
+     */
+    private Long tokenExpire = 60 * 60 * 24L;
+    /**
+     * token 的秘钥(长度为64)
+     */
+    private String tokenSecret = "qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm12";
+    /**
+     * 基于rsa的加解密方式
+     */
+    private SecurityRsa securityRsa;
+    /**
+     * 自定义token过滤器的拦截白名单
+     */
+    private List<String> whitelist;
+
+    @Setter
+    @Getter
+    public static class SecurityRsa {
+        private String publicKey;
+        private String privateKey;
+    }
+}

+ 61 - 21
service-base/src/main/java/com/simuwang/shiro/config/ShiroConfig.java

@@ -1,11 +1,16 @@
-package com.simuwang.shiro.config;
+package com.simuwang.base.config;
 
-import com.simuwang.shiro.PropertiesUtil;
+import cn.hutool.core.map.MapUtil;
+import com.simuwang.base.components.ShiroLoginAuthAdapter;
+import com.simuwang.base.components.ShiroRsaCredentialsMatcher;
 import com.simuwang.shiro.core.ShiroDbRealm;
-import com.simuwang.shiro.core.adapter.AuthAdapter;
-import com.simuwang.shiro.core.adapter.SimpleAuthAdapter;
+import com.simuwang.shiro.core.adapter.LoginAuthAdapter;
 import com.simuwang.shiro.core.bridge.AuthBridgeService;
 import com.simuwang.shiro.core.impl.ShiroDbRealmImpl;
+import com.simuwang.shiro.core.jwt.JwtFilter;
+import com.simuwang.shiro.core.jwt.JwtUtil;
+import jakarta.servlet.Filter;
+import org.apache.shiro.authc.credential.CredentialsMatcher;
 import org.apache.shiro.spring.LifecycleBeanPostProcessor;
 import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
 import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
@@ -13,16 +18,19 @@ import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
 import org.apache.shiro.web.servlet.SimpleCookie;
 import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
 import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.DependsOn;
 
-import java.util.LinkedHashMap;
-import java.util.List;
+import java.util.HashMap;
 import java.util.Map;
 
 @Configuration
 public class ShiroConfig {
+    @Autowired
+    private DaqProperties properties;
+
     /**
      * @Description 保证实现了Shiro内部lifecycle函数的bean执行
      */
@@ -31,9 +39,15 @@ public class ShiroConfig {
         return new LifecycleBeanPostProcessor();
     }
 
+    /**
+     * 注册的登录认证适配器,实际场景中需要自定义实现
+     *
+     * @return /
+     */
     @Bean
-    public AuthAdapter authAdapter() {
-        return new SimpleAuthAdapter();
+    public LoginAuthAdapter authAdapter() {
+//        return new SimpleLoginAuthAdapter();
+        return new ShiroLoginAuthAdapter(this.properties);
     }
 
     @Bean
@@ -63,11 +77,26 @@ public class ShiroConfig {
     }
 
     /**
+     * 自定义的密码匹配器,rsa
+     *
+     * @return
+     */
+    @Bean
+    public CredentialsMatcher credentialsMatcher() {
+//        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
+//        matcher.setHashAlgorithmName("SHA-256");
+//        matcher.setHashIterations(1000);
+//        matcher.setStoredCredentialsHexEncoded(true);
+//        return matcher;
+        return new ShiroRsaCredentialsMatcher(this.properties);
+    }
+
+    /**
      * @Description 自定义RealmImpl
      */
     @Bean(name = "shiroDbRealm")
     public ShiroDbRealm shiroDbRealm() {
-        return new ShiroDbRealmImpl(this.authBridgeService());
+        return new ShiroDbRealmImpl(this.authBridgeService(), this.credentialsMatcher());
     }
 
     /**
@@ -108,14 +137,24 @@ public class ShiroConfig {
      * @Description 过滤器链
      */
     private Map<String, String> filterChainDefinition() {
-        List<Object> list = PropertiesUtil.propertiesShiro.getKeyList();
-        Map<String, String> map = new LinkedHashMap<>();
-        for (Object object : list) {
-            String key = object.toString();
-            String value = PropertiesUtil.getShiroValue(key);
-//            log.info("读取防止盗链控制:---key{},---value:{}", key, value);
-            map.put(key, value);
-        }
+        Map<String, String> map = MapUtil.newHashMap(20, true);
+        map.put("/static/**", "anon");
+        map.put("/v1/login", "anon");
+        map.put("/v1/rsa-key", "anon");
+        map.put("/test/**", "anon");
+        map.put("/v1/**", "jwt");
+        map.put("/**", "jwt");
+        return map;
+    }
+
+    @Bean
+    public JwtUtil jwtUtil() {
+        return new JwtUtil(this.properties);
+    }
+
+    private Map<String, Filter> filterMap() {
+        HashMap<String, Filter> map = MapUtil.newHashMap();
+        map.put("jwt", new JwtFilter(this.properties, this.jwtUtil()));
         return map;
     }
 
@@ -125,10 +164,11 @@ public class ShiroConfig {
     @Bean("shiroFilter")
     public ShiroFilterFactoryBean shiroFilterFactoryBean() {
         ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
-        shiroFilter.setSecurityManager(defaultWebSecurityManager());
-        shiroFilter.setFilterChainDefinitionMap(filterChainDefinition());
-        shiroFilter.setLoginUrl("/login");
-        shiroFilter.setUnauthorizedUrl("/login");
+        shiroFilter.setSecurityManager(this.defaultWebSecurityManager());
+        shiroFilter.setFilters(this.filterMap());
+        shiroFilter.setFilterChainDefinitionMap(this.filterChainDefinition());
+//        shiroFilter.setLoginUrl("/v1/login");
+//        shiroFilter.setUnauthorizedUrl("/v1/login");
         return shiroFilter;
     }
 }

+ 0 - 18
service-base/src/main/java/com/simuwang/base/pojo/SysUser.java

@@ -1,18 +0,0 @@
-package com.simuwang.base.pojo;
-
-import lombok.Getter;
-import lombok.Setter;
-
-@Setter
-@Getter
-public class SysUser {
-    private String userId;
-
-    private String username;
-
-    private String realName;
-
-    private String salt;
-
-    private String password;
-}

+ 0 - 142
service-base/src/main/java/com/simuwang/shiro/LinkProperties.java

@@ -1,142 +0,0 @@
-package com.simuwang.shiro;
-
-import java.io.*;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.Properties;
-
-public class LinkProperties extends Properties {
-    private List<Object> keyList = new ArrayList<Object>();
-
-    /**
-     * 默认构造方法
-     */
-    public LinkProperties() {
-
-    }
-
-    /**
-     * 从指定路径加载信息到Properties
-     *
-     * @param path
-     */
-    public LinkProperties(String path) {
-        try {
-            InputStream is = new FileInputStream(path);
-            this.load(is);
-        } catch (FileNotFoundException e) {
-            e.printStackTrace();
-            throw new RuntimeException("指定文件不存在!");
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
-    }
-
-    /**
-     * 重写put方法,按照property的存入顺序保存key到keyList,遇到重复的后者将覆盖前者。
-     */
-    @Override
-    public synchronized Object put(Object key, Object value) {
-        this.removeKeyIfExists(key);
-        keyList.add(key);
-        return super.put(key, value);
-    }
-
-
-    /**
-     * 重写remove方法,删除属性时清除keyList中对应的key。
-     */
-    @Override
-    public synchronized Object remove(Object key) {
-        this.removeKeyIfExists(key);
-        return super.remove(key);
-    }
-
-    /**
-     * keyList中存在指定的key时则将其删除
-     */
-    private void removeKeyIfExists(Object key) {
-        keyList.remove(key);
-    }
-
-    /**
-     * 获取Properties中key的有序集合
-     *
-     * @return
-     */
-    public List<Object> getKeyList() {
-        return keyList;
-    }
-
-    /**
-     * 保存Properties到指定文件,默认使用UTF-8编码
-     *
-     * @param path 指定文件路径
-     */
-    public void store(String path) {
-        this.store(path, "UTF-8");
-    }
-
-    /**
-     * 保存Properties到指定文件,并指定对应存放编码
-     *
-     * @param path    指定路径
-     * @param charset 文件编码
-     */
-    public void store(String path, String charset) {
-        if (path != null && !"".equals(path)) {
-            try {
-                OutputStream os = new FileOutputStream(path);
-                BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os, charset));
-                this.store(bw, null);
-                bw.close();
-            } catch (FileNotFoundException e) {
-                e.printStackTrace();
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
-        } else {
-            throw new RuntimeException("存储路径不能为空!");
-        }
-    }
-
-    /**
-     * 重写keys方法,返回根据keyList适配的Enumeration,且保持HashTable keys()方法的原有语义,
-     * 每次都调用返回一个新的Enumeration对象,且和之前的不产生冲突
-     */
-    @Override
-    public synchronized Enumeration<Object> keys() {
-        return new EnumerationAdapter<Object>(keyList);
-    }
-
-    /**
-     * List到Enumeration的适配器
-     */
-    private class EnumerationAdapter<T> implements Enumeration<T> {
-        private final List<T> list;
-        private final boolean isEmpty;
-        private int index = 0;
-
-        public EnumerationAdapter(List<T> list) {
-            this.list = list;
-            this.isEmpty = list.isEmpty();
-        }
-
-        public boolean hasMoreElements() {
-            //isEmpty的引入是为了更贴近HashTable原有的语义,在HashTable中添加元素前调用其keys()方法获得一个Enumeration的引用,
-            //之后往HashTable中添加数据后,调用之前获取到的Enumeration的hasMoreElements()将返回false,但如果此时重新获取一个
-            //Enumeration的引用,则新Enumeration的hasMoreElements()将返回true,而且之后对HashTable数据的增、删、改都是可以在
-            //nextElement中获取到的。
-            return !isEmpty && index < list.size();
-        }
-
-        public T nextElement() {
-            if (this.hasMoreElements()) {
-                return list.get(index++);
-            }
-            return null;
-        }
-
-    }
-}

+ 0 - 32
service-base/src/main/java/com/simuwang/shiro/PropertiesUtil.java

@@ -1,32 +0,0 @@
-package com.simuwang.shiro;
-
-import cn.hutool.core.util.StrUtil;
-
-public class PropertiesUtil {
-    public static LinkProperties propertiesShiro = new LinkProperties();
-
-    /**
-     * 读取properties配置文件信息
-     */
-    static {
-        String sysName = System.getProperty("sys.name");
-        if (StrUtil.isBlank(sysName)) {
-            sysName = "authc.properties";
-        } else {
-            sysName += ".properties";
-        }
-        try {
-            propertiesShiro.load(PropertiesUtil.class.getClassLoader()
-                    .getResourceAsStream(sysName));
-        } catch (Exception e) {
-//            log.warn("资源路径中不存在authentication.properties权限文件,忽略读取!");
-        }
-    }
-
-    /**
-     * 根据key得到value的值
-     */
-    public static String getShiroValue(String key) {
-        return propertiesShiro.getProperty(key);
-    }
-}

+ 4 - 16
service-base/src/main/java/com/simuwang/shiro/core/ShiroToken.java

@@ -3,29 +3,17 @@ package com.simuwang.shiro.core;
 import org.apache.shiro.authc.UsernamePasswordToken;
 
 public class ShiroToken extends UsernamePasswordToken {
-    private String tokenType;
-
     private String quickPassword;
 
-    public ShiroToken(String tokenType, String username,String password) {
-        super(username,password);
-        this.tokenType = tokenType;
+    public ShiroToken(String username, String password) {
+        super(username, password);
     }
 
-    public ShiroToken(String tokenType, String username,String password,String quickPassword) {
-        super(username,password);
-        this.tokenType = tokenType;
+    public ShiroToken(String username, String password, String quickPassword) {
+        super(username, password);
         this.quickPassword = quickPassword;
     }
 
-    public String getTokenType() {
-        return tokenType;
-    }
-
-    public void setTokenType(String tokenType) {
-        this.tokenType = tokenType;
-    }
-
     public String getQuickPassword() {
         return quickPassword;
     }

+ 3 - 0
service-base/src/main/java/com/simuwang/shiro/core/ShiroUser.java

@@ -1,5 +1,6 @@
 package com.simuwang.shiro.core;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import lombok.Getter;
 import lombok.Setter;
 
@@ -19,8 +20,10 @@ public class ShiroUser {
 
     private String realName;
 
+    @JsonIgnore
     private String salt;
 
+    @JsonIgnore
     private String password;
 
     private List<String> permissionCodes;

+ 3 - 3
service-base/src/main/java/com/simuwang/shiro/core/adapter/AuthAdapter.java

@@ -1,6 +1,6 @@
 package com.simuwang.shiro.core.adapter;
 
-import com.simuwang.base.pojo.SysUser;
+import com.simuwang.shiro.core.ShiroUser;
 
 import java.util.List;
 
@@ -9,8 +9,8 @@ import java.util.List;
  * @date 2024/9/6 17:44
  * @description 认证与授权的用户信息适配器(访问数据库和缓存)
  */
-public interface AuthAdapter {
-    SysUser findUserByUsername(String username);
+public interface LoginAuthAdapter {
+    ShiroUser findUserByUsername(String username);
 
     List<String> findRoleByUserId(String userId);
 

+ 8 - 7
service-base/src/main/java/com/simuwang/shiro/core/adapter/SimpleAuthAdapter.java

@@ -1,8 +1,8 @@
 package com.simuwang.shiro.core.adapter;
 
 import cn.hutool.core.collection.ListUtil;
-import com.simuwang.base.pojo.SysUser;
-import org.springframework.stereotype.Component;
+import com.simuwang.shiro.core.ShiroUser;
+import org.apache.shiro.crypto.hash.SimpleHash;
 
 import java.util.List;
 
@@ -11,13 +11,14 @@ import java.util.List;
  * @date 2024/9/6 17:44
  * @description 认证与授权的用户信息适配器(访问数据库和缓存)
  */
-@Component
-public class SimpleAuthAdapter implements AuthAdapter {
+public class SimpleLoginAuthAdapter implements LoginAuthAdapter {
     @Override
-    public SysUser findUserByUsername(String username) {
-        SysUser sysUser = new SysUser();
+    public ShiroUser findUserByUsername(String username) {
+        ShiroUser sysUser = new ShiroUser();
         sysUser.setUserId("1");
         sysUser.setUsername("admin");
+        sysUser.setPassword(new SimpleHash("SHA-256", "123456", "1234567890abcdef", 1000).toHex());
+        sysUser.setSalt("1234567890abcdef");
         return sysUser;
     }
 
@@ -28,6 +29,6 @@ public class SimpleAuthAdapter implements AuthAdapter {
 
     @Override
     public List<String> findPermissionsByUserId(String userId) {
-        return ListUtil.toList("1");
+        return ListUtil.toList("RS:ALL");
     }
 }

+ 7 - 10
service-base/src/main/java/com/simuwang/shiro/core/bridge/AuthBridgeService.java

@@ -1,9 +1,7 @@
 package com.simuwang.shiro.core.bridge;
 
-import cn.hutool.core.bean.BeanUtil;
-import com.simuwang.base.pojo.SysUser;
 import com.simuwang.shiro.core.ShiroUser;
-import com.simuwang.shiro.core.adapter.AuthAdapter;
+import com.simuwang.shiro.core.adapter.LoginAuthAdapter;
 import org.apache.shiro.authz.AuthorizationInfo;
 import org.apache.shiro.authz.SimpleAuthorizationInfo;
 import org.springframework.stereotype.Service;
@@ -17,15 +15,14 @@ import java.util.List;
  */
 @Service
 public class AuthBridgeService {
-    private final AuthAdapter authAdapter;
+    private final LoginAuthAdapter loginAuthAdapter;
 
-    public AuthBridgeService(AuthAdapter authAdapter) {
-        this.authAdapter = authAdapter;
+    public AuthBridgeService(LoginAuthAdapter loginAuthAdapter) {
+        this.loginAuthAdapter = loginAuthAdapter;
     }
 
     public ShiroUser findUserByUsername(String username) {
-        SysUser sysUser = this.authAdapter.findUserByUsername(username);
-        return BeanUtil.copyProperties(sysUser, ShiroUser.class);
+        return this.loginAuthAdapter.findUserByUsername(username);
     }
 
     public AuthorizationInfo getAuthorizationInfo(ShiroUser shiroUser) {
@@ -41,10 +38,10 @@ public class AuthBridgeService {
     }
 
     public List<String> findRoles(String userId) {
-        return this.authAdapter.findRoleByUserId(userId);
+        return this.loginAuthAdapter.findRoleByUserId(userId);
     }
 
     public List<String> findPermissions(String userId) {
-        return this.authAdapter.findPermissionsByUserId(userId);
+        return this.loginAuthAdapter.findPermissionsByUserId(userId);
     }
 }

+ 9 - 11
service-base/src/main/java/com/simuwang/shiro/core/impl/ShiroDbRealmImpl.java

@@ -1,11 +1,10 @@
 package com.simuwang.shiro.core.impl;
 
 import com.simuwang.shiro.core.ShiroDbRealm;
-import com.simuwang.shiro.core.ShiroToken;
 import com.simuwang.shiro.core.ShiroUser;
 import com.simuwang.shiro.core.bridge.AuthBridgeService;
 import org.apache.shiro.authc.*;
-import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
+import org.apache.shiro.authc.credential.CredentialsMatcher;
 import org.apache.shiro.authz.AuthorizationInfo;
 import org.apache.shiro.lang.util.ByteSource;
 import org.apache.shiro.subject.PrincipalCollection;
@@ -14,9 +13,11 @@ import org.springframework.stereotype.Component;
 @Component
 public class ShiroDbRealmImpl extends ShiroDbRealm {
     private final AuthBridgeService authBridgeService;
+    private final CredentialsMatcher credentialsMatcher;
 
-    public ShiroDbRealmImpl(AuthBridgeService authBridgeService) {
+    public ShiroDbRealmImpl(AuthBridgeService authBridgeService, CredentialsMatcher credentialsMatcher) {
         this.authBridgeService = authBridgeService;
+        this.credentialsMatcher = credentialsMatcher;
     }
 
     @Override
@@ -27,21 +28,18 @@ public class ShiroDbRealmImpl extends ShiroDbRealm {
 
     @Override
     public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
-        ShiroToken token = (ShiroToken) authenticationToken;
-        ShiroUser shiroUser = this.authBridgeService.findUserByUsername(token.getUsername());
+        String username = (String) authenticationToken.getPrincipal();
+        // 查询用户
+        ShiroUser shiroUser = this.authBridgeService.findUserByUsername(username);
         if (shiroUser == null) {
             throw new UnknownAccountException("账号不存在");
         }
         shiroUser.setPermissionCodes(this.authBridgeService.findPermissions(shiroUser.getUserId()));
-        String salt = shiroUser.getSalt();
-        String password = shiroUser.getPassword();
-        return new SimpleAuthenticationInfo(shiroUser, password, ByteSource.Util.bytes(salt), getName());
+        return new SimpleAuthenticationInfo(shiroUser, shiroUser.getPassword(), getName());
     }
 
     @Override
     public void initCredentialsMatcher() {
-        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher("bcrypt");
-        matcher.setHashIterations(1000);
-        setCredentialsMatcher(matcher);
+        this.setCredentialsMatcher(this.credentialsMatcher);
     }
 }

+ 111 - 0
service-base/src/main/java/com/simuwang/shiro/core/jwt/JwtFilter.java

@@ -0,0 +1,111 @@
+package com.simuwang.shiro.core.jwt;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import com.simuwang.base.config.DaqProperties;
+import com.smppw.common.pojo.ResultVo;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.ExpiredJwtException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.shiro.web.filter.AccessControlFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.util.AntPathMatcher;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author wangzaijun
+ * @date 2024/9/9 16:33
+ * @description 自定义的jwt token过滤器
+ */
+@Component
+public class JwtFilter extends AccessControlFilter {
+    private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    private final DaqProperties properties;
+    private final JwtUtil jwtUtil;
+
+    public JwtFilter(DaqProperties properties, JwtUtil jwtUtil) {
+        this.properties = properties;
+        this.jwtUtil = jwtUtil;
+    }
+
+    /**
+     * isAccessAllowed()判断是否携带了有效的JwtToken
+     * onAccessDenied()是没有携带JwtToken的时候进行账号密码登录,登录成功允许访问,登录失败拒绝访问
+     */
+    @Override
+    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
+        /*
+         * 1. 返回true,shiro就直接允许访问url
+         * 2. 返回false,shiro才会根据onAccessDenied的方法的返回值决定是否允许访问url
+         *  这里先让它始终返回false来使用onAccessDenied()方法
+         */
+        return false;
+    }
+
+
+    /**
+     * @param servletRequest
+     * @param servletResponse
+     * @return 返回结果为true表明登录通过
+     * @throws Exception
+     */
+    @Override
+    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
+        /*
+         *  跟前端约定将jwtToken放在请求的Header的Authorization中,Authorization:token
+         */
+        HttpServletRequest request = (HttpServletRequest) servletRequest;
+        String requestURI = request.getRequestURI();
+        if (this.logger.isInfoEnabled()) {
+            this.logger.info("{} onAccessDenied方法被调用", requestURI);
+        }
+        List<String> whitelist = this.properties.getWhitelist();
+        if (CollUtil.isNotEmpty(whitelist)) {
+            for (String path : whitelist) {
+                if (PATH_MATCHER.match(path, requestURI)) {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("接口{} 配置了白名单,不需要签名验证!", requestURI);
+                    }
+                    return true;
+                }
+            }
+        }
+        String token = request.getHeader(JwtUtil.HEADER);
+        if (StrUtil.isBlank(token)) {
+            this.onLoginFail(servletResponse, "请先登录后操作");
+            return false;
+        }
+        Claims claims;
+        try {
+            claims = this.jwtUtil.getClaimsByToken(token);
+        } catch (ExpiredJwtException e) {
+            this.onLoginFail(servletResponse, "登录已过期,请重新登录");
+            return false;
+        }
+        String username = claims.getSubject();
+        String validToken = this.jwtUtil.getUserCache(username);
+        if (!token.equals(validToken)) {
+            this.onLoginFail(servletResponse, "token非法");
+            return false;
+        }
+        return true;
+    }
+
+    //登录失败要执行的方法
+    private void onLoginFail(ServletResponse response, String msg) throws IOException {
+        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
+        httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        httpServletResponse.setCharacterEncoding("utf-8");
+        httpServletResponse.getWriter().print(JSONUtil.toJsonStr(ResultVo.fail(20001, msg)));
+    }
+}

+ 108 - 0
service-base/src/main/java/com/simuwang/shiro/core/jwt/JwtUtil.java

@@ -0,0 +1,108 @@
+package com.simuwang.shiro.core.jwt;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.exceptions.JWTDecodeException;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.simuwang.base.config.DaqProperties;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.security.Keys;
+import org.springframework.stereotype.Component;
+
+import javax.crypto.SecretKey;
+import java.nio.charset.StandardCharsets;
+import java.sql.Timestamp;
+import java.time.LocalDateTime;
+import java.util.Date;
+import java.util.Map;
+
+@Component
+public class JwtUtil {
+    public static final String HEADER = "Authorization";
+    //    private static final String SECRET = "qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm12";
+//    private static final long EXPIRE = 60 * 24 * 7;
+    private static final Cache<String, String> USER_TOKEN_CACHE = Caffeine.newBuilder().maximumSize(16384L).build();
+    private final DaqProperties properties;
+    public JwtUtil(DaqProperties properties) {
+        this.properties = properties;
+    }
+
+//    public static void main(String[] args) {
+//        JwtUtil jwtUtil = new JwtUtil();
+//        String token = jwtUtil.generateToken("admin");
+//        System.out.println("token = " + token);
+//
+//        Claims claims = jwtUtil.getClaimsByToken(token);
+//        System.out.println("claims = " + claims);
+//
+//        String username = jwtUtil.getClaimFiled(token, "username");
+//        System.out.println("username = " + username);
+//    }
+
+    public void setUserCache(String token) {
+        String username = this.getClaimsByToken(token).getSubject();
+        USER_TOKEN_CACHE.put(username, token);
+    }
+
+    public String getUserCache(String username) {
+        return USER_TOKEN_CACHE.getIfPresent(username);
+    }
+
+    public void cleanUserCache(String username) {
+        USER_TOKEN_CACHE.put(username, null);
+    }
+
+    /**
+     * 生成jwt token
+     */
+    public String generateToken(String username) {
+        SecretKey signingKey = Keys.hmacShaKeyFor(this.properties.getTokenSecret().getBytes(StandardCharsets.UTF_8));
+        //过期时间
+        LocalDateTime tokenExpirationTime = LocalDateTime.now().plusMinutes(this.properties.getTokenExpire());
+        return Jwts.builder()
+                .signWith(signingKey, Jwts.SIG.HS512)
+                .header().add("typ", "JWT").and()
+                .issuedAt(Timestamp.valueOf(LocalDateTime.now()))
+                .subject(username)
+                .expiration(Timestamp.valueOf(tokenExpirationTime))
+                .claims(Map.of("username", username))
+                .compact();
+    }
+
+    public Claims getClaimsByToken(String token) {
+        SecretKey signingKey = Keys.hmacShaKeyFor(this.properties.getTokenSecret().getBytes(StandardCharsets.UTF_8));
+        return Jwts.parser()
+                .verifyWith(signingKey)
+                .build()
+                .parseSignedClaims(token)
+                .getPayload();
+    }
+
+    /**
+     * 检查token是否过期
+     *
+     * @return true:过期
+     */
+    public boolean isTokenExpired(Date expiration) {
+        return expiration.before(new Date());
+    }
+
+    /**
+     * 获得token中的自定义信息,一般是获取token的username,无需secret解密也能获得
+     *
+     * @param token
+     * @param filed
+     * @return
+     */
+    public String getClaimFiled(String token, String filed) {
+        try {
+            DecodedJWT jwt = JWT.decode(token);
+            return jwt.getClaim(filed).asString();
+        } catch (JWTDecodeException e) {
+//            log.error("JwtUtil getClaimFiled error: ", e);
+            return null;
+        }
+    }
+}

+ 0 - 5
service-base/src/main/resources/authc.properties

@@ -1,5 +0,0 @@
-/static/**=anon
-
-/login/**=anon
-
-/**=authc

+ 67 - 54
service-deploy/src/main/java/com/simuwang/deploy/components/ErrorInfoBuilder.java

@@ -1,25 +1,26 @@
 package com.simuwang.deploy.components;
 
+import cn.hutool.core.exceptions.ExceptionUtil;
 import cn.hutool.core.util.StrUtil;
+import com.simuwang.base.common.exception.APIException;
 import com.simuwang.base.common.exception.ErrorInfo;
 import jakarta.servlet.ServletException;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.shiro.authz.UnauthenticatedException;
+import org.apache.shiro.authz.UnauthorizedException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.boot.autoconfigure.web.ErrorProperties;
-import org.springframework.boot.autoconfigure.web.ServerProperties;
 import org.springframework.core.Ordered;
 import org.springframework.http.HttpStatus;
 import org.springframework.lang.Nullable;
 import org.springframework.stereotype.Component;
 import org.springframework.web.servlet.HandlerExceptionResolver;
 import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.resource.NoResourceFoundException;
 import org.springframework.web.util.WebUtils;
 
-import java.io.PrintWriter;
-import java.io.StringWriter;
 import java.time.LocalDateTime;
 
 /**
@@ -34,17 +35,17 @@ public class ErrorInfoBuilder implements HandlerExceptionResolver, Ordered {
      */
     private final static String ERROR_NAME = "simuwang.error";
     private final Logger logger = LoggerFactory.getLogger(this.getClass());
-    /**
-     * 错误配置(ErrorConfiguration)
-     */
-    private final ErrorProperties errorProperties;
-
-    /**
-     * 错误构造器 (Constructor) 传递配置属性:server.xx -> server.error.xx
-     */
-    public ErrorInfoBuilder(ServerProperties serverProperties) {
-        this.errorProperties = serverProperties.getError();
-    }
+//    /**
+//     * 错误配置(ErrorConfiguration)
+//     */
+//    private final ErrorProperties errorProperties;
+//
+//    /**
+//     * 错误构造器 (Constructor) 传递配置属性:server.xx -> server.error.xx
+//     */
+//    public ErrorInfoBuilder(ServerProperties serverProperties) {
+//        this.errorProperties = serverProperties.getError();
+//    }
 
     /**
      * 构建错误信息.(ErrorInfo)
@@ -61,11 +62,23 @@ public class ErrorInfoBuilder implements HandlerExceptionResolver, Ordered {
         ErrorInfo errorInfo = new ErrorInfo();
         errorInfo.setTime(LocalDateTime.now().toString());
         errorInfo.setUrl(url);
-        errorInfo.setError(error.getMessage());
+        String msg;
+        if (error instanceof NoResourceFoundException) {
+            msg = "请求资源找不到";
+        } else if (error instanceof UnauthorizedException) {
+            msg = "没有对应接口的权限";
+        } else if (error instanceof APIException e) {
+            msg = e.getMsg();
+        } else if (error instanceof UnauthenticatedException e) {
+            msg = e.getMessage();
+        } else {
+            msg = error.getMessage();
+        }
+        errorInfo.setError(msg);
         errorInfo.setStatusCode(getHttpStatus(request).value());
         errorInfo.setReasonPhrase(getHttpStatus(request).getReasonPhrase());
-        errorInfo.setStackTrace(getStackTraceInfo(error, isIncludeStackTrace(request)));
-        logger.error(StrUtil.format("{} 接口请求错误:{}", url, errorInfo.getError()));
+        errorInfo.setStackTrace(ExceptionUtil.stacktraceToString(error));
+        logger.error(StrUtil.format("{} 接口请求错误:{}", url, errorInfo.getStackTrace()));
         return errorInfo;
     }
 
@@ -112,43 +125,43 @@ public class ErrorInfoBuilder implements HandlerExceptionResolver, Ordered {
         }
     }
 
-    /**
-     * 获取堆栈轨迹(StackTrace)
-     *
-     * @see org.springframework.boot.web.servlet.error.DefaultErrorAttributes  #addStackTrace
-     */
-    public String getStackTraceInfo(Throwable error, boolean flag) {
-        if (!flag) {
-            return "omitted";
-        }
-        StringWriter stackTrace = new StringWriter();
-        error.printStackTrace(new PrintWriter(stackTrace));
-        stackTrace.flush();
-        return stackTrace.toString();
-    }
-
-    /**
-     * 判断是否包含堆栈轨迹.(isIncludeStackTrace)
-     *
-     * @see org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController #isIncludeStackTrace
-     */
-    public boolean isIncludeStackTrace(HttpServletRequest request) {
-
-        //读取错误配置(server.error.include-stacktrace=NEVER)
-        ErrorProperties.IncludeAttribute includeStacktrace = errorProperties.getIncludeStacktrace();
+//    /**
+//     * 获取堆栈轨迹(StackTrace)
+//     *
+//     * @see org.springframework.boot.web.servlet.error.DefaultErrorAttributes  #addStackTrace
+//     */
+//    public String getStackTraceInfo(Throwable error, boolean flag) {
+//        if (!flag) {
+//            return "omitted";
+//        }
+//        StringWriter stackTrace = new StringWriter();
+//        error.printStackTrace(new PrintWriter(stackTrace));
+//        stackTrace.flush();
+//        return stackTrace.toString();
+//    }
 
-        //情况1:若IncludeStacktrace为ALWAYS
-        if (includeStacktrace == ErrorProperties.IncludeAttribute.ALWAYS) {
-            return true;
-        }
-        //情况2:若请求参数含有trace
-        if (includeStacktrace == ErrorProperties.IncludeAttribute.ON_PARAM) {
-            String parameter = request.getParameter("trace");
-            return parameter != null && !"false".equalsIgnoreCase(parameter);
-        }
-        //情况3:其它情况
-        return false;
-    }
+//    /**
+//     * 判断是否包含堆栈轨迹.(isIncludeStackTrace)
+//     *
+//     * @see org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController #isIncludeStackTrace
+//     */
+//    public boolean isIncludeStackTrace(HttpServletRequest request) {
+//
+//        //读取错误配置(server.error.include-stacktrace=NEVER)
+//        ErrorProperties.IncludeAttribute includeStacktrace = errorProperties.getIncludeStacktrace();
+//
+//        //情况1:若IncludeStacktrace为ALWAYS
+//        if (includeStacktrace == ErrorProperties.IncludeAttribute.ALWAYS) {
+//            return true;
+//        }
+//        //情况2:若请求参数含有trace
+//        if (includeStacktrace == ErrorProperties.IncludeAttribute.ON_PARAM) {
+//            String parameter = request.getParameter("trace");
+//            return parameter != null && !"false".equalsIgnoreCase(parameter);
+//        }
+//        //情况3:其它情况
+//        return false;
+//    }
 
     /**
      * 保存错误/异常.

+ 1 - 1
service-deploy/src/main/java/com/simuwang/deploy/config/GlobalResponseBodyAdvice.java

@@ -22,7 +22,7 @@ import java.util.Arrays;
 /**
  * @author wangzaijun
  * @date 2023/8/12 16:39
- * @description 全局的结果处理工具
+ * @description 全局的结果处理工具(匿名访问的接口不走这里,还要排查原因)
  */
 @RestControllerAdvice
 public class GlobalResponseBodyAdvice implements ResponseBodyAdvice<Object> {

File diff suppressed because it is too large
+ 13 - 0
service-deploy/src/main/resources/application.yml


+ 68 - 18
service-manage/src/main/java/com/simuwang/manage/api/LoginController.java

@@ -1,18 +1,68 @@
-//package com.simuwang.manage.api;
-//
-//import com.simuwang.shiro.core.ShiroToken;
-//import org.apache.shiro.SecurityUtils;
-//import org.apache.shiro.subject.Subject;
-//import org.springframework.web.bind.annotation.PostMapping;
-//import org.springframework.web.bind.annotation.RequestMapping;
-//import org.springframework.web.bind.annotation.RequestParam;
-//import org.springframework.web.bind.annotation.RestController;
-//
-//@RestController
-//@RequestMapping("/")
-//public class LoginController {
-//    @PostMapping("login")
-//    public String login() {
-//        return "/login";
-//    }
-//}
+package com.simuwang.manage.api;
+
+import cn.hutool.core.map.MapUtil;
+import com.simuwang.base.config.DaqProperties;
+import com.simuwang.manage.dto.LoginUser;
+import com.simuwang.shiro.core.ShiroToken;
+import com.simuwang.shiro.core.jwt.JwtUtil;
+import com.smppw.common.pojo.ResultVo;
+import com.smppw.common.pojo.enums.status.ResultCode;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.IncorrectCredentialsException;
+import org.apache.shiro.authc.UnknownAccountException;
+import org.apache.shiro.authz.annotation.RequiresAuthentication;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.apache.shiro.subject.Subject;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+@RestController
+@RequestMapping("/v1")
+public class LoginController {
+    private final JwtUtil jwtUtil;
+    private final DaqProperties properties;
+
+    public LoginController(JwtUtil jwtUtil, DaqProperties properties) {
+        this.jwtUtil = jwtUtil;
+        this.properties = properties;
+    }
+
+    @GetMapping("rsa-key")
+    public Map<String, Object> getRsaKey() {
+        return MapUtil.<String, Object>builder("rsaKey", this.properties.getSecurityRsa().getPublicKey()).build();
+    }
+
+    @PostMapping("login")
+    public ResultVo<String> login(@RequestBody LoginUser loginUser, HttpServletResponse response) {
+        String token;
+        try {
+            ShiroToken shiroToken = new ShiroToken(loginUser.getUsername(), loginUser.getPassword());
+            Subject subject = SecurityUtils.getSubject();
+            subject.login(shiroToken);
+
+            token = jwtUtil.generateToken(loginUser.getUsername());
+            this.jwtUtil.setUserCache(token);
+            response.setHeader(JwtUtil.HEADER, token);
+            response.setHeader("Access-control-Expost-Headers", JwtUtil.HEADER);
+        } catch (UnknownAccountException | IncorrectCredentialsException exception) {
+            return ResultVo.fail(ResultCode.AUTH_FAILD);
+        }
+        return ResultVo.ok(ResultCode.SUCCESS.getCode(), "登录成功", token);
+    }
+
+    @RequiresAuthentication
+    @PostMapping("/logout")
+    public void logout() {
+        Subject subject = SecurityUtils.getSubject();
+        this.jwtUtil.cleanUserCache(subject.getPrincipal().toString());
+        subject.logout();
+    }
+
+    @RequiresPermissions("RS:TEST")
+    @GetMapping("/user-info")
+    public Object getUserInfo() {
+        return SecurityUtils.getSubject().getPrincipal();
+    }
+}

+ 16 - 0
service-manage/src/main/java/com/simuwang/manage/api/TestController.java

@@ -1,14 +1,30 @@
 package com.simuwang.manage.api;
 
+import cn.hutool.crypto.asymmetric.KeyType;
+import cn.hutool.crypto.asymmetric.RSA;
+import com.simuwang.base.config.DaqProperties;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 @RestController
 @RequestMapping("/test")
 public class TestController {
+    private final DaqProperties properties;
+
+    public TestController(DaqProperties properties) {
+        this.properties = properties;
+    }
+
     @GetMapping("hello")
     public String test() {
         return "hello";
     }
+
+    @GetMapping("rsa")
+    public String rsa(@RequestParam("str") String str) {
+        String publicKey = this.properties.getSecurityRsa().getPublicKey();
+        return new RSA(null, publicKey).encryptBase64(str, KeyType.PublicKey);
+    }
 }

+ 22 - 0
service-manage/src/main/java/com/simuwang/manage/dto/LoginUser.java

@@ -0,0 +1,22 @@
+package com.simuwang.manage.dto;
+
+public class LoginUser {
+    private String username;
+    private String password;
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+}