初步配置安全模块,并处理管理操作

This commit is contained in:
筱锋xiao_lfeng 2024-01-14 17:46:07 +08:00
parent a1817ac53f
commit 92a4a07e09
Signed by: XiaoLFeng
GPG Key ID: F693AA12AABBFA87
18 changed files with 263 additions and 93 deletions

21
pom.xml
View File

@ -64,9 +64,28 @@
<!-- Shiro --> <!-- Shiro -->
<dependency> <dependency>
<groupId>org.apache.shiro</groupId> <groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId> <artifactId>shiro-spring-boot-starter</artifactId>
<version>1.9.1</version> <version>1.9.1</version>
</dependency> </dependency>
<!-- Jwt Token -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.3</version>
<scope>runtime</scope>
</dependency>
<!-- SpringBoot Test --> <!-- SpringBoot Test -->
<dependency> <dependency>
<groupId>org.mybatis.spring.boot</groupId> <groupId>org.mybatis.spring.boot</groupId>

View File

@ -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;
}
}

View File

@ -1,6 +1,7 @@
package com.jsl.oa.config; 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 lombok.RequiredArgsConstructor;
import org.apache.shiro.authc.*; import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.AuthorizationInfo;
@ -10,7 +11,7 @@ import org.apache.shiro.subject.PrincipalCollection;
@RequiredArgsConstructor @RequiredArgsConstructor
public class MyRealm extends AuthorizingRealm { public class MyRealm extends AuthorizingRealm {
private final AccountService accountService; private final UserService userService;
/** /**
* 授权 * 授权
@ -30,11 +31,21 @@ public class MyRealm extends AuthorizingRealm {
*/ */
@Override @Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; JwtToken jwtToken = (JwtToken) authenticationToken;
Account account = accountService.findByUsername(token.getUsername()); String username = jwtToken.getUsername();
if(account != null){
return new SimpleAuthenticationInfo(account,account.getPassword(),getName()); // 从数据库获取用户信息
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());
} }
} }

View File

@ -1,44 +1,46 @@
package com.jsl.oa.config; 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.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager; 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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import java.util.HashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
@Configuration @Configuration
@RequiredArgsConstructor
public class ShiroConfiguration { public class ShiroConfiguration {
@Bean private final UserService userService;
public ShiroFilterFactoryBean filterFactoryBean(@Qualifier("manager") DefaultWebSecurityManager manager){
// ShiroFilterFactoryBean 用来配置拦截规则
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(manager);
// 设置拦截规则
Map<String,String> 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;
}
@Bean @Bean
public DefaultWebSecurityManager manager(@Qualifier("myRealm") MyRealm myRealm){ public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
manager.setRealm(myRealm); shiroFilterFactoryBean.setSecurityManager(securityManager);
return manager;
// 配置过滤器规则
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/auth/**", "anon"); // 登录接口允许匿名访问
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
// 设置登录接口
shiroFilterFactoryBean.setLoginUrl("/unauthorized");
return shiroFilterFactoryBean;
} }
@Bean @Bean
public MyRealm myRealm(){ public DefaultWebSecurityManager securityManager(MyRealm realm) {
return new MyRealm(); DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
return securityManager;
}
@Bean
public MyRealm myRealm() {
return new MyRealm(userService);
} }
} }

View File

@ -10,26 +10,40 @@ import com.jsl.oa.utils.ResultUtil;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.text.ParseException; import java.text.ParseException;
/**
* <h1>用户认证控制器</h1>
* <hr/>
* 用户认证控制器包含用户注册用户登录用户登出接口
*
* @since v1.0.0
* @version v1.1.0
* @see AuthService
* @see UserRegisterVO
* @see UserLoginVO
* @see BaseResponse
* @see ErrorCode
* @see Processing
* @see ResultUtil
*/
@RestController @RestController
@RequiredArgsConstructor @RequiredArgsConstructor
public class AuthController { public class AuthController {
private final AuthService authService; private final AuthService authService;
/** /**
* <h1>用户注册</h1> * <h2>用户注册</h2>
* <hr/> * <hr/>
* 用户注册接口 * 用户注册接口
* *
* @since v1.0.0
* @return {@link BaseResponse} * @return {@link BaseResponse}
* @author 筱锋xiao_lfeng * @author 筱锋xiao_lfeng
*/ */
@PostMapping("/user/register") @PostMapping("/auth/register")
public BaseResponse authRegister(@RequestBody @Validated UserRegisterVO userRegisterVO, BindingResult bindingResult) throws ParseException { public BaseResponse authRegister(@RequestBody @Validated UserRegisterVO userRegisterVO, BindingResult bindingResult) throws ParseException {
// 判断是否有参数错误 // 判断是否有参数错误
if (bindingResult.hasErrors()) { if (bindingResult.hasErrors()) {
@ -39,7 +53,7 @@ public class AuthController {
} }
/** /**
* <h1>用户登录</h1> * <h2>用户登录</h2>
* <hr/> * <hr/>
* 用户登录接口 * 用户登录接口
* *
@ -48,7 +62,7 @@ public class AuthController {
* @return {@link BaseResponse} * @return {@link BaseResponse}
* @author 176yunxuan * @author 176yunxuan
*/ */
@PostMapping("/user/login") @GetMapping("/auth/login")
public BaseResponse authLogin(@RequestBody @Validated UserLoginVO userLoginVO, BindingResult bindingResult){ public BaseResponse authLogin(@RequestBody @Validated UserLoginVO userLoginVO, BindingResult bindingResult){
// 判断是否有参数错误 // 判断是否有参数错误
if (bindingResult.hasErrors()) { if (bindingResult.hasErrors()) {
@ -56,4 +70,8 @@ public class AuthController {
} }
return authService.authLogin(userLoginVO); return authService.authLogin(userLoginVO);
} }
public BaseResponse authLogout() {
return null;
}
} }

View File

@ -19,4 +19,9 @@ public class CustomController implements ErrorController {
public ResponseEntity<BaseResponse> handleError() { public ResponseEntity<BaseResponse> handleError() {
return ResultUtil.error("PageNotFound", 404, "请求资源不存在"); return ResultUtil.error("PageNotFound", 404, "请求资源不存在");
} }
@RequestMapping("/unauthorized")
public ResponseEntity<BaseResponse> handleUnauthorized() {
return ResultUtil.error("Unauthorized", 401, "未授权");
}
} }

View File

@ -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;
}
}

View File

@ -10,19 +10,19 @@ import org.apache.ibatis.annotations.Select;
public interface UserMapper { public interface UserMapper {
@Select("SELECT * FROM organize_oa.oa_user WHERE username = #{username}") @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}") @Select("SELECT * FROM organize_oa.oa_user WHERE job_id = #{jobId}")
UserDO getUserByUserNum(String userNum); UserDO getUserByUserNum(String jobId);
@Insert("INSERT INTO organize_oa.oa_user (user_num, username, password, sex, age, unit, field, hometown, kind, state) " + @Insert("INSERT INTO organize_oa.oa_user " +
"VALUES " + "(job_id, username, password, address, phone, email, age, signature, avatar, nickname, account_no_locked, description, updated_at) " +
"(#{userNum}, #{username}, #{password}, #{sex}, #{age}, #{unit}, #{filed}, #{hometown}, #{kind}, #{state})") "VALUES (#{jobId}, #{username}, #{password}, #{address}, #{phone}, #{email}, #{age}, #{signature}, #{avatar}, #{nickname}, #{accountNoLocked}, #{description}, #{updatedAt})")
Boolean insertUser(UserDO userDO); 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} ") @Select("SELECT password FROM organize_oa.oa_user WHERE job_id = #{jobId}")
UserDO login(UserLoginVO userLoginVO);
@Select("select password from organize_oa.oa_user where user_num = #{userNum}")
String loginPassword(UserLoginVO userLoginVO); String loginPassword(UserLoginVO userLoginVO);
@Select("SELECT * FROM organize_oa.oa_user WHERE job_id = #{jobId}")
UserDO login(UserLoginVO userLoginVO);
} }

View File

@ -20,7 +20,7 @@ import java.sql.Timestamp;
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public class UserDO { public class UserDO {
private Long id; private Long id;
private Long jobId; private String jobId;
private String username; private String username;
private String password; private String password;
private String address; private String address;

View File

@ -17,7 +17,7 @@ import javax.validation.constraints.Pattern;
@Getter @Getter
public class UserLoginVO { public class UserLoginVO {
@Pattern(regexp = "^[0-9A-Z]+$", message = "工号格式错误") @Pattern(regexp = "^[0-9A-Z]+$", message = "工号格式错误")
private String userNum; private String jobId;
@NotBlank(message = "密码不能为空") @NotBlank(message = "密码不能为空")
private String password; private String password;
} }

View File

@ -23,11 +23,11 @@ public class UserRegisterVO {
@NotBlank(message = "密码不能为空") @NotBlank(message = "密码不能为空")
private String password; private String password;
@Pattern(regexp = "^(男|女|保密)$", message = "性别只能为男、女或保密") @Pattern(regexp = "^[012]$", message = "保密:0,男:1,女:2")
private String sex; private Short sex;
@NotBlank(message = "年龄不能为空") @NotBlank(message = "年龄不能为空")
private String age; private Short age;
@NotBlank(message = "单位不能为空") @NotBlank(message = "单位不能为空")
private String unit; private String unit;

View File

@ -1,7 +0,0 @@
package com.jsl.oa.services;
import org.apache.shiro.authc.Account;
public interface AccountService {
Account findByUsername(String username);
}

View File

@ -0,0 +1,25 @@
package com.jsl.oa.services;
import com.jsl.oa.model.doData.UserDO;
/**
* <h1>用户控制器接口</h1>
* <hr/>
*
* <p>该接口用于定义用户控制器的方法</p>
*
* @version 1.1.0
* @since v1.1.0
* @author 筱锋xiao_lfeng
*/
public interface UserService {
/**
* <h2>根据用户名获取用户信息</h2>
*
* <p>该方法用于根据用户名获取用户信息</p>
*
* @param username 用户名
* @return 用户信息
*/
UserDO getUserInfoByUsername(String username);
}

View File

@ -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);
}
}

View File

@ -14,9 +14,7 @@ import lombok.RequiredArgsConstructor;
import org.mindrot.jbcrypt.BCrypt; import org.mindrot.jbcrypt.BCrypt;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.sql.Date;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@ -33,9 +31,9 @@ public class AuthServiceImpl implements AuthService {
* @throws ParseException 日期转换异常 * @throws ParseException 日期转换异常
*/ */
@Override @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) { if (getUserByUsername != null) {
return ResultUtil.error(ErrorCode.USER_EXIST); return ResultUtil.error(ErrorCode.USER_EXIST);
@ -46,20 +44,15 @@ public class AuthServiceImpl implements AuthService {
do { do {
userNum = Processing.createJobNumber((short) 2); userNum = Processing.createJobNumber((short) 2);
} while (userMapper.getUserByUserNum(userNum) != null); } while (userMapper.getUserByUserNum(userNum) != null);
// 处理性别
// 数据上传 // 数据上传
Date getDate = new Date(new SimpleDateFormat("yyyy-MM-dd").parse(userRegisterVO.getAge()).getTime());
UserDO userDO = new UserDO(); UserDO userDO = new UserDO();
userDO.πsetUserNum(userNum) userDO.setJobId(userNum)
.setUsername(userRegisterVO.getUsername()) .setUsername(userRegisterVO.getUsername())
.setPassword(BCrypt.hashpw(userRegisterVO.getPassword(), BCrypt.gensalt())) .setPassword(BCrypt.hashpw(userRegisterVO.getPassword(), BCrypt.gensalt()))
.setSex(userRegisterVO.getSex()) .setSex(userRegisterVO.getSex())
.setAge(getDate) .setAge(userRegisterVO.getAge());
.setUnit(userRegisterVO.getUnit())
.setFiled(userRegisterVO.getFiled())
.setHometown(userRegisterVO.getHometown())
.setKind("0")
.setState("0");
// 插入数据 // 插入数据
if (userMapper.insertUser(userDO)) { if (userMapper.insertUser(userDO)) {
userDO.setPassword(null); userDO.setPassword(null);

View File

@ -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);
}
}

View File

@ -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<Claims> claimsJws = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token);
// 从JWT中获取用户名进行匹配
String tokenUsername = claimsJws.getBody().getSubject();
// 验证用户名是否匹配
return username.equals(tokenUsername);
} catch (Exception e) {
// 验证失败
return false;
}
}
}

View File

@ -1,5 +1,6 @@
package com.jsl.oa; package com.jsl.oa;
import com.jsl.oa.utils.JwtUtil;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
@ -8,6 +9,12 @@ class JslOrganizeInternalOaApplicationTests {
@Test @Test
void contextLoads() { void contextLoads() {
String token = JwtUtil.generateToken("admin");
if (JwtUtil.verify(token, "admin")) {
System.out.println("验证通过");
} else {
System.out.println("验证失败");
}
} }
} }