diff --git a/pom.xml b/pom.xml index 9791ec4..0292c13 100644 --- a/pom.xml +++ b/pom.xml @@ -64,9 +64,28 @@ org.apache.shiro - shiro-spring + shiro-spring-boot-starter 1.9.1 + + + io.jsonwebtoken + jjwt-api + 0.11.3 + + + io.jsonwebtoken + jjwt-impl + 0.11.3 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.11.3 + runtime + + org.mybatis.spring.boot diff --git a/src/main/java/com/jsl/oa/config/JwtToken.java b/src/main/java/com/jsl/oa/config/JwtToken.java new file mode 100644 index 0000000..c3dde62 --- /dev/null +++ b/src/main/java/com/jsl/oa/config/JwtToken.java @@ -0,0 +1,23 @@ +package com.jsl.oa.config; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.apache.shiro.authc.AuthenticationToken; + +@Getter +@RequiredArgsConstructor +public class JwtToken implements AuthenticationToken { + + private final String token; + private final String username; + + @Override + public Object getPrincipal() { + return token; + } + + @Override + public Object getCredentials() { + return token; + } +} \ No newline at end of file diff --git a/src/main/java/com/jsl/oa/config/MyRealm.java b/src/main/java/com/jsl/oa/config/MyRealm.java index 76d01ce..740eed8 100644 --- a/src/main/java/com/jsl/oa/config/MyRealm.java +++ b/src/main/java/com/jsl/oa/config/MyRealm.java @@ -1,6 +1,7 @@ package com.jsl.oa.config; -import com.jsl.oa.services.AccountService; +import com.jsl.oa.model.doData.UserDO; +import com.jsl.oa.services.UserService; import lombok.RequiredArgsConstructor; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; @@ -10,7 +11,7 @@ import org.apache.shiro.subject.PrincipalCollection; @RequiredArgsConstructor public class MyRealm extends AuthorizingRealm { - private final AccountService accountService; + private final UserService userService; /** * 授权 @@ -30,11 +31,21 @@ public class MyRealm extends AuthorizingRealm { */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { - UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; - Account account = accountService.findByUsername(token.getUsername()); - if(account != null){ - return new SimpleAuthenticationInfo(account,account.getPassword(),getName()); + JwtToken jwtToken = (JwtToken) authenticationToken; + String username = jwtToken.getUsername(); + + // 从数据库获取用户信息 + UserDO userDO = userService.getUserInfoByUsername(username); + if (userDO == null) { + throw new UnknownAccountException("用户不存在"); + } else if (!userDO.getAccountNoLocked()) { + throw new LockedAccountException("用户已被锁定"); + } else if (!userDO.getEnabled()) { + throw new DisabledAccountException("用户已被禁用"); + } else if (!userDO.getAccountNoExpired()) { + throw new ExpiredCredentialsException("用户已过期"); } - return null; + + return new SimpleAuthenticationInfo(username, jwtToken.getCredentials(), getName()); } } diff --git a/src/main/java/com/jsl/oa/config/ShiroConfiguration.java b/src/main/java/com/jsl/oa/config/ShiroConfiguration.java index 046672d..ec861a6 100644 --- a/src/main/java/com/jsl/oa/config/ShiroConfiguration.java +++ b/src/main/java/com/jsl/oa/config/ShiroConfiguration.java @@ -1,44 +1,46 @@ package com.jsl.oa.config; +import com.jsl.oa.services.UserService; +import lombok.RequiredArgsConstructor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; @Configuration +@RequiredArgsConstructor public class ShiroConfiguration { - @Bean - public ShiroFilterFactoryBean filterFactoryBean(@Qualifier("manager") DefaultWebSecurityManager manager){ - // ShiroFilterFactoryBean 用来配置拦截规则 - ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); - factoryBean.setSecurityManager(manager); - // 设置拦截规则 - Map map = new HashMap<>(); - map.put("/main","authc"); - map.put("/manage","perms[manage]"); - map.put("/administrator","roles[administrator]"); - factoryBean.setFilterChainDefinitionMap(map); - //未授权页面 - factoryBean.setUnauthorizedUrl("/unauth"); - return factoryBean; - } - - + private final UserService userService; @Bean - public DefaultWebSecurityManager manager(@Qualifier("myRealm") MyRealm myRealm){ - DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); - manager.setRealm(myRealm); - return manager; + public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) { + ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); + shiroFilterFactoryBean.setSecurityManager(securityManager); + + // 配置过滤器规则 + Map filterChainDefinitionMap = new LinkedHashMap<>(); + filterChainDefinitionMap.put("/auth/**", "anon"); // 登录接口允许匿名访问 + + shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); + + // 设置登录接口 + shiroFilterFactoryBean.setLoginUrl("/unauthorized"); + return shiroFilterFactoryBean; } @Bean - public MyRealm myRealm(){ - return new MyRealm(); + public DefaultWebSecurityManager securityManager(MyRealm realm) { + DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); + securityManager.setRealm(realm); + return securityManager; + } + + @Bean + public MyRealm myRealm() { + return new MyRealm(userService); } } diff --git a/src/main/java/com/jsl/oa/controllers/AuthController.java b/src/main/java/com/jsl/oa/controllers/AuthController.java index 7497693..c7bb20a 100644 --- a/src/main/java/com/jsl/oa/controllers/AuthController.java +++ b/src/main/java/com/jsl/oa/controllers/AuthController.java @@ -10,26 +10,40 @@ import com.jsl.oa.utils.ResultUtil; import lombok.RequiredArgsConstructor; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.text.ParseException; +/** + *

用户认证控制器

+ *
+ * 用户认证控制器,包含用户注册、用户登录、用户登出接口 + * + * @since v1.0.0 + * @version v1.1.0 + * @see AuthService + * @see UserRegisterVO + * @see UserLoginVO + * @see BaseResponse + * @see ErrorCode + * @see Processing + * @see ResultUtil + */ @RestController @RequiredArgsConstructor public class AuthController { private final AuthService authService; /** - *

用户注册

+ *

用户注册

*
* 用户注册接口 * + * @since v1.0.0 * @return {@link BaseResponse} * @author 筱锋xiao_lfeng */ - @PostMapping("/user/register") + @PostMapping("/auth/register") public BaseResponse authRegister(@RequestBody @Validated UserRegisterVO userRegisterVO, BindingResult bindingResult) throws ParseException { // 判断是否有参数错误 if (bindingResult.hasErrors()) { @@ -39,7 +53,7 @@ public class AuthController { } /** - *

用户登录

+ *

用户登录

*
* 用户登录接口 * @@ -48,7 +62,7 @@ public class AuthController { * @return {@link BaseResponse} * @author 176yunxuan */ - @PostMapping("/user/login") + @GetMapping("/auth/login") public BaseResponse authLogin(@RequestBody @Validated UserLoginVO userLoginVO, BindingResult bindingResult){ // 判断是否有参数错误 if (bindingResult.hasErrors()) { @@ -56,4 +70,8 @@ public class AuthController { } return authService.authLogin(userLoginVO); } + + public BaseResponse authLogout() { + return null; + } } diff --git a/src/main/java/com/jsl/oa/controllers/CustomController.java b/src/main/java/com/jsl/oa/controllers/CustomController.java index 668bd3f..7f62c84 100644 --- a/src/main/java/com/jsl/oa/controllers/CustomController.java +++ b/src/main/java/com/jsl/oa/controllers/CustomController.java @@ -19,4 +19,9 @@ public class CustomController implements ErrorController { public ResponseEntity handleError() { return ResultUtil.error("PageNotFound", 404, "请求资源不存在"); } + + @RequestMapping("/unauthorized") + public ResponseEntity handleUnauthorized() { + return ResultUtil.error("Unauthorized", 401, "未授权"); + } } diff --git a/src/main/java/com/jsl/oa/dao/UserDAO.java b/src/main/java/com/jsl/oa/dao/UserDAO.java new file mode 100644 index 0000000..9ac1bd7 --- /dev/null +++ b/src/main/java/com/jsl/oa/dao/UserDAO.java @@ -0,0 +1,24 @@ +package com.jsl.oa.dao; + +import com.jsl.oa.mapper.UserMapper; +import com.jsl.oa.model.doData.UserDO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class UserDAO { + + private final UserMapper userMapper; + + public UserDO getUserInfoByUsername(String username) { + UserDO userDO = null; + // 从 Redis 获取数据 + // TODO: 10000-Redis: 从 Redis 获取数据 + // 从数据库获取用户信息 + if (userDO == null) { + userDO = userMapper.getUserInfoByUsername(username); + } + return userDO; + } +} diff --git a/src/main/java/com/jsl/oa/mapper/UserMapper.java b/src/main/java/com/jsl/oa/mapper/UserMapper.java index d0d9e6f..a7d1bd6 100644 --- a/src/main/java/com/jsl/oa/mapper/UserMapper.java +++ b/src/main/java/com/jsl/oa/mapper/UserMapper.java @@ -10,19 +10,19 @@ import org.apache.ibatis.annotations.Select; public interface UserMapper { @Select("SELECT * FROM organize_oa.oa_user WHERE username = #{username}") - UserDO getUserByUsername(String username); + UserDO getUserInfoByUsername(String username); - @Select("SELECT * FROM organize_oa.oa_user WHERE user_num = #{userNum}") - UserDO getUserByUserNum(String userNum); + @Select("SELECT * FROM organize_oa.oa_user WHERE job_id = #{jobId}") + UserDO getUserByUserNum(String jobId); - @Insert("INSERT INTO organize_oa.oa_user (user_num, username, password, sex, age, unit, field, hometown, kind, state) " + - "VALUES " + - "(#{userNum}, #{username}, #{password}, #{sex}, #{age}, #{unit}, #{filed}, #{hometown}, #{kind}, #{state})") - Boolean insertUser(UserDO userDO); + @Insert("INSERT INTO organize_oa.oa_user " + + "(job_id, username, password, address, phone, email, age, signature, avatar, nickname, account_no_locked, description, updated_at) " + + "VALUES (#{jobId}, #{username}, #{password}, #{address}, #{phone}, #{email}, #{age}, #{signature}, #{avatar}, #{nickname}, #{accountNoLocked}, #{description}, #{updatedAt})") + boolean insertUser(UserDO userDO); - @Select("select id, user_num, username, sex, age, unit, field, hometown, kind, state from organize_oa.oa_user where user_num = #{userNum} ") - UserDO login(UserLoginVO userLoginVO); - - @Select("select password from organize_oa.oa_user where user_num = #{userNum}") + @Select("SELECT password FROM organize_oa.oa_user WHERE job_id = #{jobId}") String loginPassword(UserLoginVO userLoginVO); + + @Select("SELECT * FROM organize_oa.oa_user WHERE job_id = #{jobId}") + UserDO login(UserLoginVO userLoginVO); } diff --git a/src/main/java/com/jsl/oa/model/doData/UserDO.java b/src/main/java/com/jsl/oa/model/doData/UserDO.java index b4e8179..184c5a2 100644 --- a/src/main/java/com/jsl/oa/model/doData/UserDO.java +++ b/src/main/java/com/jsl/oa/model/doData/UserDO.java @@ -20,7 +20,7 @@ import java.sql.Timestamp; @JsonInclude(JsonInclude.Include.NON_NULL) public class UserDO { private Long id; - private Long jobId; + private String jobId; private String username; private String password; private String address; diff --git a/src/main/java/com/jsl/oa/model/voData/UserLoginVO.java b/src/main/java/com/jsl/oa/model/voData/UserLoginVO.java index 47356a6..25c4ce6 100644 --- a/src/main/java/com/jsl/oa/model/voData/UserLoginVO.java +++ b/src/main/java/com/jsl/oa/model/voData/UserLoginVO.java @@ -17,7 +17,7 @@ import javax.validation.constraints.Pattern; @Getter public class UserLoginVO { @Pattern(regexp = "^[0-9A-Z]+$", message = "工号格式错误") - private String userNum; + private String jobId; @NotBlank(message = "密码不能为空") private String password; } diff --git a/src/main/java/com/jsl/oa/model/voData/UserRegisterVO.java b/src/main/java/com/jsl/oa/model/voData/UserRegisterVO.java index c8b8c7a..6745d96 100644 --- a/src/main/java/com/jsl/oa/model/voData/UserRegisterVO.java +++ b/src/main/java/com/jsl/oa/model/voData/UserRegisterVO.java @@ -23,11 +23,11 @@ public class UserRegisterVO { @NotBlank(message = "密码不能为空") private String password; - @Pattern(regexp = "^(男|女|保密)$", message = "性别只能为男、女或保密") - private String sex; + @Pattern(regexp = "^[012]$", message = "保密:0,男:1,女:2") + private Short sex; @NotBlank(message = "年龄不能为空") - private String age; + private Short age; @NotBlank(message = "单位不能为空") private String unit; diff --git a/src/main/java/com/jsl/oa/services/AccountService.java b/src/main/java/com/jsl/oa/services/AccountService.java deleted file mode 100644 index 5baf437..0000000 --- a/src/main/java/com/jsl/oa/services/AccountService.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.jsl.oa.services; - -import org.apache.shiro.authc.Account; - -public interface AccountService { - Account findByUsername(String username); -} diff --git a/src/main/java/com/jsl/oa/services/UserService.java b/src/main/java/com/jsl/oa/services/UserService.java new file mode 100644 index 0000000..d0ebeb9 --- /dev/null +++ b/src/main/java/com/jsl/oa/services/UserService.java @@ -0,0 +1,25 @@ +package com.jsl.oa.services; + +import com.jsl.oa.model.doData.UserDO; + +/** + *

用户控制器接口

+ *
+ * + *

该接口用于定义用户控制器的方法

+ * + * @version 1.1.0 + * @since v1.1.0 + * @author 筱锋xiao_lfeng + */ +public interface UserService { + /** + *

根据用户名获取用户信息

+ * + *

该方法用于根据用户名获取用户信息

+ * + * @param username 用户名 + * @return 用户信息 + */ + UserDO getUserInfoByUsername(String username); +} diff --git a/src/main/java/com/jsl/oa/services/impl/AccountServiceImpl.java b/src/main/java/com/jsl/oa/services/impl/AccountServiceImpl.java deleted file mode 100644 index 740105f..0000000 --- a/src/main/java/com/jsl/oa/services/impl/AccountServiceImpl.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.jsl.oa.services.impl; - -import com.jsl.oa.services.AccountService; -import lombok.RequiredArgsConstructor; -import org.apache.shiro.authc.Account; - -@RequiredArgsConstructor -public class AccountServiceImpl implements AccountService { - - private final AccountMapper accountMapper; - - @Override - public Account findByUsername(String username) { - QueryWrapper wrapper = new QueryWrapper(); - wrapper.eq("username",username); - return accountMapper.selectOne(wrapper); - } -} diff --git a/src/main/java/com/jsl/oa/services/impl/AuthServiceImpl.java b/src/main/java/com/jsl/oa/services/impl/AuthServiceImpl.java index 2272001..a282f0b 100644 --- a/src/main/java/com/jsl/oa/services/impl/AuthServiceImpl.java +++ b/src/main/java/com/jsl/oa/services/impl/AuthServiceImpl.java @@ -14,9 +14,7 @@ import lombok.RequiredArgsConstructor; import org.mindrot.jbcrypt.BCrypt; import org.springframework.stereotype.Service; -import java.sql.Date; import java.text.ParseException; -import java.text.SimpleDateFormat; @Service @RequiredArgsConstructor @@ -33,9 +31,9 @@ public class AuthServiceImpl implements AuthService { * @throws ParseException 日期转换异常 */ @Override - public BaseResponse authRegister(UserRegisterVO userRegisterVO) throws ParseException { + public BaseResponse authRegister(UserRegisterVO userRegisterVO) { // 用户检查是否存在 - UserDO getUserByUsername = userMapper.getUserByUsername(userRegisterVO.getUsername()); + UserDO getUserByUsername = userMapper.getUserInfoByUsername(userRegisterVO.getUsername()); // 用户名已存在 if (getUserByUsername != null) { return ResultUtil.error(ErrorCode.USER_EXIST); @@ -46,20 +44,15 @@ public class AuthServiceImpl implements AuthService { do { userNum = Processing.createJobNumber((short) 2); } while (userMapper.getUserByUserNum(userNum) != null); + // 处理性别 // 数据上传 - Date getDate = new Date(new SimpleDateFormat("yyyy-MM-dd").parse(userRegisterVO.getAge()).getTime()); UserDO userDO = new UserDO(); - userDO.πsetUserNum(userNum) + userDO.setJobId(userNum) .setUsername(userRegisterVO.getUsername()) .setPassword(BCrypt.hashpw(userRegisterVO.getPassword(), BCrypt.gensalt())) .setSex(userRegisterVO.getSex()) - .setAge(getDate) - .setUnit(userRegisterVO.getUnit()) - .setFiled(userRegisterVO.getFiled()) - .setHometown(userRegisterVO.getHometown()) - .setKind("0") - .setState("0"); + .setAge(userRegisterVO.getAge()); // 插入数据 if (userMapper.insertUser(userDO)) { userDO.setPassword(null); diff --git a/src/main/java/com/jsl/oa/services/impl/UserServiceImpl.java b/src/main/java/com/jsl/oa/services/impl/UserServiceImpl.java new file mode 100644 index 0000000..e5c9a98 --- /dev/null +++ b/src/main/java/com/jsl/oa/services/impl/UserServiceImpl.java @@ -0,0 +1,19 @@ +package com.jsl.oa.services.impl; + +import com.jsl.oa.dao.UserDAO; +import com.jsl.oa.model.doData.UserDO; +import com.jsl.oa.services.UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class UserServiceImpl implements UserService { + + private final UserDAO userDAO; + + @Override + public UserDO getUserInfoByUsername(String username) { + return userDAO.getUserInfoByUsername(username); + } +} diff --git a/src/main/java/com/jsl/oa/utils/JwtUtil.java b/src/main/java/com/jsl/oa/utils/JwtUtil.java new file mode 100644 index 0000000..adf9801 --- /dev/null +++ b/src/main/java/com/jsl/oa/utils/JwtUtil.java @@ -0,0 +1,49 @@ +package com.jsl.oa.utils; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; + +import java.security.Key; + +public class JwtUtil { + // 替换为实际的密钥,建议使用足够长的随机字符串 + private static final String SECRET_KEY = Processing.createJobNumber((short) 1, (short) 255); + + // Token 有效期,这里设置为一天,可以根据实际需求调整 + private static final long EXPIRATION_TIME = 86400000; + + // 生成Token + public static String generateToken(String username) { + Key key = Keys.hmacShaKeyFor(SECRET_KEY.getBytes()); + + return Jwts.builder() + .setSubject(username) + .setExpiration(new java.util.Date(System.currentTimeMillis() + EXPIRATION_TIME)) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + } + + // 验证Token + public static boolean verify(String token, String username) { + try { + Key key = Keys.hmacShaKeyFor(SECRET_KEY.getBytes()); + + Jws claimsJws = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token); + + // 从JWT中获取用户名进行匹配 + String tokenUsername = claimsJws.getBody().getSubject(); + + // 验证用户名是否匹配 + return username.equals(tokenUsername); + } catch (Exception e) { + // 验证失败 + return false; + } + } +} diff --git a/src/test/java/com/jsl/oa/JslOrganizeInternalOaApplicationTests.java b/src/test/java/com/jsl/oa/JslOrganizeInternalOaApplicationTests.java index 8c08a2e..96601ad 100644 --- a/src/test/java/com/jsl/oa/JslOrganizeInternalOaApplicationTests.java +++ b/src/test/java/com/jsl/oa/JslOrganizeInternalOaApplicationTests.java @@ -1,5 +1,6 @@ package com.jsl.oa; +import com.jsl.oa.utils.JwtUtil; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @@ -8,6 +9,12 @@ class JslOrganizeInternalOaApplicationTests { @Test void contextLoads() { + String token = JwtUtil.generateToken("admin"); + if (JwtUtil.verify(token, "admin")) { + System.out.println("验证通过"); + } else { + System.out.println("验证失败"); + } } }