学习博客链接:https://blog.csdn.net/wws_p/article/details/107126321(万分感谢)
CREATE
DATABASE `shiro`;
USE
`shiro`;
DROP TABLE IF EXISTS `role_per`;
CREATE TABLE `role_per`
(
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '表主键id',
`roleId` varchar(32) DEFAULT NULL COMMENT '角色表的主键id',
`perId` varchar(32) DEFAULT NULL COMMENT '权限表的主键id',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
insert into `role_per`(`id`, `roleId`, `perId`)
values (1, '100', 'M01'),
(2, '100', 'M02'),
(3, '100', 'M03'),
(4, '200', 'M204'),
(5, '100', 'M204');
DROP TABLE IF EXISTS `sys_permissions`;
CREATE TABLE `sys_permissions`
(
`perId` varchar(32) NOT NULL COMMENT '权限表id 作为表主键 用于关联',
`permissionsName` varchar(32) DEFAULT NULL COMMENT '权限名称',
`perRemarks` varchar(255) DEFAULT NULL COMMENT '备注,预留字段',
PRIMARY KEY (`perId`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
insert into `sys_permissions`(`perId`, `permissionsName`, `perRemarks`)
values ('M01', 'resetPassword', '重置密码'),
('M02', 'querySystemLog', '查看系统日志'),
('M03', 'exportUserInfo', '导出用户信息'),
('M204', 'queryMyUserInfo', '查看个人信息');
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`
(
`roleId` varchar(32) NOT NULL COMMENT ' 角色id 作为表主键 用于关联',
`roleName` varchar(32) DEFAULT NULL COMMENT '角色名',
`roleRemarks` varchar(255) DEFAULT NULL COMMENT '备注,预留字段',
PRIMARY KEY (`roleId`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
insert into `sys_role`(`roleId`, `roleName`, `roleRemarks`)
values ('100', 'admin', '系统管理员'),
('200', 'common', '普通用户');
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`
(
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'user表的id字段',
`userId` varchar(255) NOT NULL COMMENT '用户id 作为表主键 用于关联',
`userName` varchar(25) DEFAULT NULL COMMENT '用户登录帐号',
`password` varchar(255) DEFAULT NULL COMMENT '用户登录密码',
`userRemarks` varchar(255) DEFAULT NULL COMMENT '备注,预留字段',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
insert into `sys_user`(`id`, `userId`, `userName`, `password`, `userRemarks`)
values (1, '10001', 'adminHong', '202cb962ac59075b964b07152d234b70', '小红'),
(2, '15564819717', '2', 'f10bfea990b657f6e8355c6e3ee67e2d', 'hehe'),
(3, '177900', '武文生', 'c4dbde004d409a733e4a7c8b00466613', '生生酱'),
(4, '20001', 'jc', 'e165421110ba03099a1c0393373c5b43', 'JC'),
(5, '18864819717', 'myDear', '756b3c2e758a2b8c728fa2e4d3f3294d', 'nice today i love you'),
(8, '18262699169', 'hello', '4cad6da13952ad1621e4f8ede54d9fad', 'hello'),
(9, '15698756214', 'good luck', '713741121ec6b5d854b9c15e78a36f27', 'good luck'),
(10, '12345678922', '测试一下', 'a753bbccb874ef05b43b9fceffb949dd', '闲来无聊');
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role`
(
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '表主键id',
`userId` varchar(255) DEFAULT NULL COMMENT '帐号表的主键id',
`roleId` varchar(32) DEFAULT NULL COMMENT '角色表的主键id',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
insert into `user_role`(`id`, `userId`, `roleId`)
values (1, '10001', '100'),
(2, '20001', '200'),
(3, '177900', '200'),
(4, '15564819717', '200'),
(5, '18864819717', '200'),
(8, '18262699169', '200'),
(9, '15698756214', '200'),
(10, '12345678922', '200');
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.10version>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.0.1version>
dependency>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.3.2version>
dependency>
<dependency>
<groupId>javax.persistencegroupId>
<artifactId>persistence-apiartifactId>
<version>1.0version>
dependency>
<dependency>
<groupId>com.auth0groupId>
<artifactId>java-jwtartifactId>
<version>3.4.0version>
dependency>
<dependency>
<groupId>com.google.guavagroupId>
<artifactId>guavaartifactId>
<version>28.1-jreversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<scope>testscope>
dependency>
dependencies>
# pom.xml中添加配置
<build>
<resources>
<resource>
<directory>src/main/javadirectory>
<includes>
<include>**/*.xmlinclude>
includes>
resource>
resources>
build>
# 端口
server.port=8081
# 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/shiro?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=GMT%2B8
spring.datasource.password=123456
spring.datasource.username=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# mybatis配置
mybatis.mapper-locations=classpath:com.example.mapper/*.xml
mybatis.type-aliases-package=com.example.pojo
# 开启驼峰命名
mybatis.configuration.map-underscore-to-camel-case=true
@SpringBootApplication
@MapperScan("com.example.mapper")
public class ShiroJwtApplication {
public static void main(String[] args) {
SpringApplication.run(ShiroJwtApplication.class, args);
}
}
@Getter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false, of = "userId")
@Builder
@Table(name = "sys_user")
@Accessors(chain = true)
public class User {
private Integer id;
private String userId;
private String userName;
private String password;
private String userRemarks;
public static User getUser(String userId, String userName, String encryptPassword, String remark) {
return User.builder().userId(userId).userName(userName).password(encryptPassword).userRemarks(remark).build();
}
}
Mapper接口
@Mapper
public interface UserMapper {
User selectById(@Param("userId") String userId);
LinkedHashMap<String, Object> selectUserPermissionById(@Param("userId") String userId);
User selectByName(@Param("userName") String userName);
LinkedHashMap<String, Object> selectUserPermissionByName(@Param("userName") String userId);
void insertUser(@Param("user") User user);
}
对应的xml文件
INSERT into sys_user
(userId, userName, password, userRemarks)
VALUES (#{user.userId}, #{user.userName}, #{user.password}, #{user.userRemarks})
测试类进行测试
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ShiroJwtApplication.class)
public class ShiroApplicationTest {
@Autowired
private UserMapper userMapper;
@Test
public void test01() {
LinkedHashMap<String, Object> map = userMapper.selectUserPermissionById("10001");
System.out.println(map);
}
}
@Getter
@Setter
@ToString
public class RegisterDto {
//用户手机号
@NotNull(message = "用户输入的手机号不可为空")
@Size(max = 11, min = 11, message = "手机号必须为11位")
private String userId;
//用户输入的密码
@NotBlank(message = "用户输入的注册密码不可为空")
@Size(max = 8, min = 6, message = "用户输入的密码长度必须在6-8之间")
private String password;
//用户名称
private String userName;
//描述
private String remark;
}
@PostMapping("/user/register")
public Map<String, Object> userRegister(@RequestBody @Valid RegisterDto registerDto){
return userService.register(registerDto.getUserId(),registerDto.getUserName(),registerDto.getPassword(),registerDto.getRemark());
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private UserRoleMapper userRoleMapper;
@Override
public Map<String, Object> passWordLogin(String userId, String passWord) {
HashMap<String, Object> map = new HashMap<>();
// 首先检查此用户是否在数据库
if (this.userMapper.selectById(userId) != null) {
map.put("code", "500");
map.put("msg", "用户id已存在");
return map;
}
// 制作用户密码,然后将用户插入user表中
// SimpleHash(加密的算法名称,密码,随机盐,散列次数)
String newPassword = new SimpleHash("md5", password, userId, 1024).toHex();
log.info("加密之后的用户密码是:{}", newPassword);
this.insetUser(User.getUser(userId, userName, newPassword, remark));
// 增加用户角色中间表,注册最基本角色
userRoleMapper.insert(userId, 200);
map.put("code", 200);
map.put("msg", "注册成功");
return map;
}
@Override
public HashMap<String, Object> register(String userId, String userName, String password, String remark) {
return userDomainService.register(userId, userName, password, remark);
}
@Override
public void sendVerificationCode(String userId) {
}
@Override
public String verificationCodeLogin(String userId, String code) {
return null;
}
}
整合思路:登录的时候要求用户输入手机号+密码登录,登录之后首先检查有无此用户,没有提示用户不存在,有的话继续检查用户输入的密码是否正确,正确为用户返回jwt制作的token,错误进行友情提示。
@Getter
@Setter
@ToString
public class PassWordLoginDto {
@NotNull(message = "用户密码登录传递的id不能为空")
private String userId;
@NotBlank(message = "用户密码登录传递的密码名称不能为空")
private String password;
}
@PostMapping(value = "/user/passwordLogin", name = "用户密码登录")
public Map<String, Object> passwordLogin(@RequestBody @Valid PassWordLoginDto passWordLoginDTO) {
log.info("传递的请求参数:{}", passWordLoginDTO);
return userService.passWordLogin(passWordLoginDTO.getUserId(),passWordLoginDTO.getPassword());
}
public Map<String, Object> passWordLogin(String userId, String passWord) {
HashMap<String, Object> map = new HashMap<>();
// 获取Subject
Subject subject = SecurityUtils.getSubject();
// 校验userId是否为空
if (StringUtils.isEmpty(userId)) {
map.put("code", 500);
map.put("msg", "账户不存在");
return map;
}
// 校验数据库中此user是否存在
User user = this.userMapper.selectById(userId);
if (user == null) {
map.put("code", 500);
map.put("msg", "用户不存在");
return map;
}
// 制作CustomizedToken执行登录
CustomizedToken customizedToken = new CustomizedToken(userId, passWord, LoginEnum.BY_PASSWORD.getLoginType());
subject.login(customizedToken);
// 若登陆成功,生成token返回给前端
String sign = JwtUtil.sign(user.getUserId(), Constant.TOKEN_SECRET);
map.put("code", 200);
// 返回Token信息
map.put("token", sign);
return map;
}
public class CustomizedToken extends UsernamePasswordToken {
/**
* 登录类型
*/
public String loginType;
public CustomizedToken(final String username, final String password, final String loginType) {
super(username, password);
this.loginType = loginType;
}
public String getLoginType() {
return loginType;
}
public void setLoginType(String loginType) {
this.loginType = loginType;
}
@Override
public String toString() {
return "loginType=" + loginType + ",username=" + super.getUsername() + ",password=" + String.valueOf(super.getPassword());
}
}
public class Constant {
/**
* 验证码过期时间 此处为五分钟
*/
public static final Integer CODE_EXPIRE_TIME = 60 * 5;
/**
* jwtToken过期时间 默认为30天
* public static Integer TOKEN_EXPIRE_TIME = 7 * 24 * 60 * 60 * 1000;
*/
public static final Long TOKEN_EXPIRE_TIME = 31 * 24 * 60 * 60 * 1000L;
/**
* UserId
*/
public static final String USER_ID = "userId";
/**
* token请求头名称
*/
public static final String TOKEN_HEADER_NAME = "authorization";
/*做token签名的字符串*/
public static final String TOKEN_SECRET="abc";
//token的载荷中盛放的信息 只盛放一个userId 其余什么也不再盛放
public static final String TOKEN_CLAIM="userId";
//redis存放用户验证码时给的前缀
public static final String REDIS_LOGIN_CODE="LOGIN_CODE:";
}
@Slf4j
public class JwtUtil {
/**
* 校验token是否正确
*
* @param token 密钥
* @param secret 用户的密码
* @return 是否正确
*/
public static boolean verify(String token, String secret) {
try {
//根据密码生成JWT效验器
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.withClaim(Constant.TOKEN_CLAIM, getUserId(token))
.build();
// 效验TOKEN
verifier.verify(token);
return true;
} catch (JWTVerificationException exception) {
return false;
}
}
/**
* 获得token中的信息无需secret解密也能获得
*
* @return token中包含的用户名
*/
public static String getUserId(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim(Constant.TOKEN_CLAIM).asString();
} catch (JWTDecodeException e) {
return null;
}
}
/**
* @param userId 用户id
* @param secret 制作此token的签名依据(使用Constant.TOKEN_SECRET)
* @return 加密的token
*/
public static String sign(String userId, String secret) {
Date date = new Date(System.currentTimeMillis() + Constant.TOKEN_EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(secret);
return JWT.create()
.withClaim(Constant.TOKEN_CLAIM, userId)
.withExpiresAt(date)
.sign(algorithm);
}
}
1. 将书写的Realm类交给Shiro管理
2. 进行加密规则统一处理
3. 进行ShiroFilter编写,实现请求拦截和进行多Realm处理
realm是shiro的核心概念,不同的realm用于不同情形下用户的身份认证和权限校验,写一个realm 需要继承shiro的AuthorizingRealm 。你需要完成的需重写两个方法:doGetAuthenticationInfodoGetAuthorizationInfo。分别负责刚刚提到的身份认证和权限校验。当存在多种认证的token的时候,你需要重写这里的supports方法,起到的是一个适配的作用。
原文链接:https://blog.csdn.net/wws_p/article/details/107126321
/**
* 不需要关注PasswordRealm的授权
* 用户登录之后的权限校验全部发生在token层面,由jwtRealm进行
* PasswordRealm要做的就是用户的认证
*/
@Slf4j
public class PasswordRealm extends AuthorizingRealm {
//认为realm所做的身份认证、权限校验都属于最底部的进入接口前的准备 尽量让其贴近领域层
@Autowired
private UserMapper userMapper;
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof CustomizedToken;
}
/**
* 授权操作
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
* 认证操作
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("PasswordRealm权限认证开始,传递的token:{}", authenticationToken);
//找出数据库中的对象 给定用户输入的对象做出对比
CustomizedToken token = (CustomizedToken) authenticationToken;
log.info("PasswordRealm转换的自定义token是:{}", token);
// 根据userId查询用户
User user = userMapper.selectById(token.getUsername());
if (user == null) {
// 抛出账号不存在异常
throw new UnknownAccountException();
}
Object credentials = user.getPassword();
//param1:数据库用户 param2:密码 param3:加密所用盐值 param4:当前realm的名称
return new SimpleAuthenticationInfo(user, credentials, ByteSource.Util.bytes(user.getUserId()), getName());
}
}
@Slf4j
public class JwtRealm extends AuthorizingRealm {
@Autowired
private UserMapper userMapper;
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JwtToken;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String token = principalCollection.toString();
String userId = JwtUtil.getUserId(token);
log.info("JwtRealm身份认证开始,获取到的token是:{}", token);
// 这里的空指针异常不需要处理 无论此处抛出什么异常 shiro均认为身份有问题
LinkedHashMap<String, Object> stringObjectLinkedHashMap = userMapper.selectUserPermissionById(userId);
//获取所有的角色
String roleName = String.valueOf(stringObjectLinkedHashMap.get("roleName"));
//获取所有的权限
List<String> permissionsNameList =
Splitter.on(",").splitToList(String.valueOf(stringObjectLinkedHashMap.get("permissionsNameList")));
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 添加角色
authorizationInfo.addRole(roleName);
//添加权限
authorizationInfo.addStringPermissions(permissionsNameList);
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("jwtRealm中关于身份验证的方法执行...传递的token是:{}", authenticationToken);
String token = (String) authenticationToken.getCredentials();
// 解密获得phone,用于和数据库进行对比
String userId = JwtUtil.getUserId(token);
if (userId == null) {
throw new AuthenticationException("账户不存在");
}
User user = userMapper.selectById(userId);
//校验用户是否存在
if (user == null) {
throw new AuthenticationException("用户不存在");
}
//操作时校验的是非对称加密是否成立.
if (!JwtUtil.verify(token, Constant.TOKEN_SECRET)) {
log.info("token校验无效...");
throw new AuthenticationException("token已失效");
}
log.info("进行身份验证时,用户提供的token有效");
return new SimpleAuthenticationInfo(token, token, getName());
}
}
public class JwtToken implements AuthenticationToken {
/**
* 秘钥
*/
private String token;
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
public JwtToken(String token) {
this.token = token;
}
}
@Slf4j
public class UserModularRealmAuthenticator extends ModularRealmAuthenticator {
//当subject.login()方法执行,下面的代码即将执行
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("UserModularRealmAuthenticator:method doAuthenticate() 执行 ");
// 判断getRealms()是否返回为空
assertRealmsConfigured();
// 所有Realm
Collection<Realm> realms = getRealms();
// 盛放登录类型对应的所有Realm集合
Collection<Realm> typeRealms = new ArrayList<>();
// 强制转换回自定义的Token
try {
log.info("进入了UserModularRealmAuthenticator类...得到的authenticationToken是:{}", authenticationToken);
JwtToken jwtToken = (JwtToken) authenticationToken;
for (Realm realm : realms) {
if (realm.getName().contains("Jwt")) {
typeRealms.add(realm);
}
}
return doSingleRealmAuthentication(typeRealms.iterator().next(), jwtToken);
} catch (ClassCastException e) {
typeRealms.clear();
// 这个类型转换的警告不需要再关注 如果token错误 后面将会抛出异常信息
CustomizedToken customizedToken = (CustomizedToken) authenticationToken;
// 登录类型
String loginType = customizedToken.getLoginType();
for (Realm realm : realms) {
System.out.println("real名称:" + realm.getName());
log.info("正在遍历的realm是:{}", realm.getName());
if (realm.getName().contains(loginType)) {
log.info("当前realm:{}被注入:", realm.getName());
typeRealms.add(realm);
}
}
// 判断是单Realm还是多Realm
if (typeRealms.size() == 1) {
log.info("一个realm");
return doSingleRealmAuthentication(typeRealms.iterator().next(), customizedToken);
} else {
log.info("多个realm");
return doMultiRealmAuthentication(typeRealms, customizedToken);
}
}
}
}
当subject.login()执行,传递的token将来到重写了ModularRealmAuthenticator类的类里面,在这里也就是来到了 UserModularRealmAuthenticator ,选取realm的规则代码中写的非常清楚。简单再说一下
首先尝试将token强转为jwtToken,因为不知道此时是用户在密码登录、验证码登录或者是jwt登录。所以只能尝试转换了。转换成功选择JwtRealm。强转失败,就只剩下两个选择,要么密码登录要么验证码登录,这两种登录传递的token都是CustomizedToken,此时根据CustomizedToken中的loginType字段选取realm即可。
原文链接:https://blog.csdn.net/wws_p/article/details/107126321
/**
* 这个类最主要的目的是:当请求需要校验权限,token是否具有权限时,构造出主体subject执行login()
*/
@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter {
/**
* 执行登录认证
* @param request ServletRequest
* @param response ServletResponse
* @param mappedValue mappedValue
* @return 是否成功
*/
@Override
//这个方法叫做 尝试进行登录的操作,如果token存在,那么进行提交登录,如果不存在说明可能是正在进行登录或者做其它的事情 直接放过即可
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
try {
executeLogin(request, response);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 执行登录
*/
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws ServletException, IOException {
log.info("进入JwtFilter类中...");
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String token = httpServletRequest.getHeader(Constant.TOKEN_HEADER_NAME);
log.info("获取到的token是:{}",token);
// 判断token是否存在
if (token == null) {
return false;
}
JwtToken jwtToken = new JwtToken(token);
try{
log.info("提交UserModularRealmAuthenticator决定由哪个realm执行操作...");
getSubject(request, response).login(jwtToken);
} catch (AuthenticationException e){
log.info("捕获到身份认证异常");
return false;
}
return true;
}
/**
* 对跨域提供支持
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
}
@Bean("matcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
// 设置加密算法名称
matcher.setHashAlgorithmName("MD5");
// 设置散列次数
matcher.setHashIterations(1024);
// 设置存储凭证十六进制编码
matcher.setStoredCredentialsHexEncoded(true);
return matcher;
}
/**
* shiro配置
*/
@Configuration
public class ShiroConfig {
/**
* 设置密码加密规则
*
* @return HashedCredentialsMatcher
*/
@Bean("matcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
// 设置加密算法名称
matcher.setHashAlgorithmName("MD5");
// 设置散列次数
matcher.setHashIterations(1024);
// 设置存储凭证十六进制编码
matcher.setStoredCredentialsHexEncoded(true);
return matcher;
}
/**
* 为登录Realm绑定密码加密规则
*
* @param matcher
* @return
*/
@Bean
public PasswordRealm passwordRealm(@Qualifier("matcher") HashedCredentialsMatcher matcher) {
PasswordRealm passwordRealm = new PasswordRealm();
passwordRealm.setCredentialsMatcher(matcher);
return passwordRealm;
}
@Bean("jwtRealm")
public JwtRealm jwtRealm() {
return new JwtRealm();
}
/**
* 用来处理不同的Token
*
* @return
*/
@Bean("userModularRealmAuthenticator")
public UserModularRealmAuthenticator userModularRealmAuthenticator() {
//自己重写的ModularRealmAuthenticator
UserModularRealmAuthenticator modularRealmAuthenticator = new UserModularRealmAuthenticator();
modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
return modularRealmAuthenticator;
}
/**
* Shiro内置过滤器,可以实现拦截器相关的拦截器
* 常用的过滤器:
* anon:无需认证(登录)可以访问
* authc:必须认证才可以访问
* user:如果使用rememberMe的功能可以直接访问
* perms:该资源必须得到资源权限才可以访问
* role:该资源必须得到角色权限才可以访问
**/
@Bean
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 设置 SecurityManager
bean.setSecurityManager(securityManager);
// 设置未登录跳转url
bean.setUnauthorizedUrl("/user/unLogin");
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/user/passwordLogin", "anon");
filterMap.put("/user/verificationCodeLogin", "anon");
filterMap.put("/user/register", "anon");
bean.setFilterChainDefinitionMap(filterMap);
Map<String, Filter> filter = new HashMap<>(1);
filter.put("jwt", new JwtFilter());
bean.setFilters(filter);
filterMap.put("/**", "jwt");
bean.setFilterChainDefinitionMap(filterMap);
return bean;
}
/**
* SecurityManager 是 Shiro 架构的核心,通过它来链接Realm和用户(文档中称之为Subject.)
*
* @return
*/
@Bean("securityManager")
public DefaultWebSecurityManager securityManager(@Qualifier("passwordRealm") PasswordRealm passwordRealm,
@Qualifier("jwtRealm") JwtRealm jwtRealm,
@Qualifier("userModularRealmAuthenticator") UserModularRealmAuthenticator userModularRealmAuthenticator) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
List<Realm> realms = new ArrayList<>();
// 添加多个realm
securityManager.setAuthenticator(userModularRealmAuthenticator);
realms.add(passwordRealm);
realms.add(jwtRealm);
securityManager.setRealms(realms);
/*
* 关闭shiro自带的session,详情见文档
*/
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
securityManager.setSubjectDAO(subjectDAO);
return securityManager;
}
/**
* 以下Bean开启shiro权限注解
*
* @return DefaultAdvisorAutoProxyCreator
*/
@Bean
public static DefaultAdvisorAutoProxyCreator creator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
}
使用注解对接口进行权限限制(这里主要关注JwtRealm和JwtFilter操作)
@RestController
public class PremController {
@RequiresRoles(value = {"admin", "common"}, logical = Logical.OR)
@RequiresPermissions(value = {"resetPassword", "queryMyUserInfo"}, logical = Logical.OR)
@GetMapping("/shiro/getPre")
public String getUserPermission() {
return "查询成功";
}
}
枚举
@Getter
public enum LoginEnum {
/**
* 账号密码登录
*/
BY_PASSWORD("Password"),
/**
* 账号密码登录
*/
BY_CODE("Code");
private String loginType;
LoginEnum(String loginType) {
this.loginType = loginType;
}
}
全局异常捕捉
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(value = IncorrectCredentialsException.class)
public Map<String, Object> handlerException(IncorrectCredentialsException e) {
HashMap<String, Object> map = new HashMap<>();
map.put("code", 500);
map.put("msg", "密码错误");
return map;
}
}