shiro是什么呢?其实简单来说,shiro就是一个java的安全框架,执行身份验证、授权、密码和会话管理。
这里需要重点了解一下shiro核心的API:Subject、SecurityManager、Realm
Subject:用户主体(把操作交给SecurityMangager)
SecurityManager:安全管理器(关联Realm)
Realm:Shiro连接数据的桥梁
会不多说,实践为主。
该demo使用的技术栈是SpringBoot+MyBatis+shiro,数据库是使用MySQL数据库,通过Redis进行缓存和Session会话管理,风格的话是采用restful风格,使用swagger2测试,如果不习惯的话也可以直接postman就行。
注意:这里做前后端分离的
pom.xml:
org.springframework.boot
spring-boot-starter-web
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.0
mysql
mysql-connector-java
runtime
com.alibaba
druid
1.1.9
org.apache.shiro
shiro-spring
1.4.0
io.springfox
springfox-swagger2
2.9.2
io.springfox
springfox-swagger-ui
2.9.2
org.springframework.boot
spring-boot-starter-thymeleaf
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/xxxxxx?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Hongkong
username: xxxx
password: xxxxxx
#使用阿里的druid数据源
type: com.alibaba.druid.pool.DruidDataSource
mybatis:
#实体类对应的包路径
type-aliases-package: com.bgy.entity
mapper-locations: classpath:mapper/*.xml
configuration:
use-generated-keys: true
#驼峰命名
map-underscore-to-camel-case: true
#使用类的别名
use-column-label: true
shiroConfig.class:
@Configuration
public class ShiroConfig {
/**
* 创建ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
//添加Shiro内置过滤器
/**
* Shiro内置过滤器,可以实现权限相关的拦截器
* 常用的过滤器:
* anon: 无需认证(登录)可以访问
* authc: 必须认证才可以访问
* user: 如果使用rememberMe的功能可以直接访问
* perms: 该资源必须得到资源权限才可以访问
* role: 该资源必须得到角色权限才可以访问
*/
Map filerMap = new LinkedHashMap<>();
filerMap.put("/login", "anon");
filerMap.put("/encrypt/*", "anon");
// 添加页面需要的权限
filerMap.put("/index", "perms[sys:editor]");
// 全部拦截
filerMap.put("/**", "authc");
// 修改调整的登录页面
shiroFilterFactoryBean.setLoginUrl("/toLogin");
// 登录成功后跳转的页面
shiroFilterFactoryBean.setSuccessUrl("/index");
// 设置未授权提示页面
shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filerMap);
return shiroFilterFactoryBean;
}
/**
* 创建DefaultWebSecurityManager
*/
@Bean(name = "securityManager")
public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(userRealm);
return defaultWebSecurityManager;
}
/**
* 创建Realm
*/
@Bean(name = "userRealm")
public UserRealm getRealm() {
return new UserRealm();
}
}
自定义Realm类,UserRealm.class :
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
/**
* 执行授权逻辑
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//获取当前登录用户
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getPrincipal();
User dbUser = userService.getUserInfoByName(user.getUsername());
RolePermission rolePermission = userService.getPermIdByRoleId(dbUser.getRoleId());
Permission permission = userService.getPermDetailByPermId(rolePermission.getPermId());
System.out.println("AuthorizationInfo->" + permission.getPermDetail());
//为用户添加权限
info.addStringPermission(permission.getPermDetail());
return info;
}
/**
* 执行认证逻辑
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws
AuthenticationException {
//判断用户名
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
User user = userService.getUserInfoByName(token.getUsername());
if (user == null) {
System.out.println(("用户名不存在"));
//用户名不存在
//shiro底层会抛出UnKnowAccountException
return null;
}
//判断密码
try {
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), "");
return simpleAuthenticationInfo;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
这里数据是连接数据库的,如果不连接的,可以直接静态模拟数据测试。
数据库表:
正常基于shiro框架是五张表,用户表、角色表、用户-角色表、权限表、角色-权限表,我们这里只做四表的,去掉用户-角色表
CREATE TABLE `t_user_info` (
`user_id` bigint(20) NOT NULL COMMENT '用户id',
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(255) NOT NULL COMMENT '密码',
`role_id` int(11) DEFAULT NULL COMMENT '角色id',
PRIMARY KEY (`user_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户信息表';
CREATE TABLE `sys_roles` (
`role_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`role_name` varchar(20) NOT NULL COMMENT '角色名称',
PRIMARY KEY (`role_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
CREATE TABLE `sys_permissions` (
`perm_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '权限ID',
`perm_name` varchar(255) NOT NULL COMMENT '权限名称',
`perm_detail` varchar(20) NOT NULL COMMENT '权限对应代码',
PRIMARY KEY (`perm_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
CREATE TABLE `sys_roles_permissions` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '表自增ID',
`role_id` int(11) NOT NULL COMMENT '角色ID',
`perm_id` int(11) NOT NULL COMMENT '权限ID',
PRIMARY KEY (`id`) USING BTREE,
KEY `perm_id` (`perm_id`),
KEY `role_id` (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
实体类entity:
User.class:
@Data
@ApiModel
public class User {
private long userId;
private String username;
private String password;
private int roleId;
}
Role.class:
@Data
@ApiModel
public class Role {
private int roleId;
private String roleName;
}
Permission.class:
@Data
@ApiModel
public class Permission {
private int permId;
private String permName;
private String permDetail;
}
RolePermission.class:
@Data
@ApiModel
public class RolePermission {
private int id;
private int roleId;
private int permId;
}
UserMapper.xml:
update t_user_info
set password = #{password}
where username = #{username}
数据库配置以及映射都准备好了,接下来当然是三层模型了。
UserMapper.class:
@Mapper
@Repository
public interface UserMapper {
/**
* 通过用户名查询用户信息
* @param username
* @return user
*/
public User getUserInfoByName(String username);
/**
* 根据角色id查询权限id
* @param role_id
* @return
*/
public RolePermission getPermIdByRoleId(int role_id);
/**
* 根据角色id查询权限detail
* @param perm_id
* @return
*/
public Permission getPermDetailByPermId(int perm_id);
/**
* 更新加密后的密码
* @param username
* @param password
* @return
*/
public int updatePasswordWithEncryption(String username, String password);
}
UserService.class:
public interface UserService {
/**
* 通过用户名查询用户信息
* @param username
* @return user
*/
public User getUserInfoByName(String username);
/**
* 根据角色id查询权限id
* @param role_id
* @return
*/
public RolePermission getPermIdByRoleId(int role_id);
/**
* 根据角色id查询权限detail
* @param perm_id
* @return
*/
public Permission getPermDetailByPermId(int perm_id);
/**
* 更新加密后的密码
* @param username
* @param password
* @return
*/
public int updatePasswordWithEncryption(String username, String password);
}
UserServiceImpl.class:
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
public User getUserInfoByName(String username) {
return userMapper.getUserInfoByName(username);
}
@Override
public RolePermission getPermIdByRoleId(int role_id) {
return userMapper.getPermIdByRoleId(role_id);
}
@Override
public Permission getPermDetailByPermId(int perm_id) {
return userMapper.getPermDetailByPermId(perm_id);
}
@Override
public int updatePasswordWithEncryption(String username, String password) {
return userMapper.updatePasswordWithEncryption(username, password);
}
}
UserController.class:
@Api(tags = "用户信息接口")
@Controller
public class UserInfoController {
@Resource
UserService userService;
@ControllerLog(description = "获取用户信息")
@ApiOperation(value = "获取用户信息")
@RequestMapping(value = "/user/{username}", method = RequestMethod.GET)
@ResponseBody
public Map getUserInfoById(@PathVariable String username) {
Map map = new HashMap<>(16);
User user = userService.getUserInfoByName(username);
map.put("username", user.getUsername());
map.put("roleId", user.getRoleId());
RolePermission rolePermission = userService.getPermIdByRoleId(user.getRoleId());
Permission permission = userService.getPermDetailByPermId(rolePermission.getPermId());
map.put("permDetail", permission.getPermDetail());
return map;
}
@ApiOperation(value = "拦截后返回登录页的接口")
@RequestMapping(value = "/toLogin", method = RequestMethod.GET)
public Object toLogin() {
Map map = new HashMap<>(16);
map.put("code", 200);
map.put("msg", "未登录");
return "/login";
}
@ApiOperation(value = "登录测试页面的接口")
@RequestMapping(value = "/login", method = RequestMethod.POST)
public Object login(@RequestParam String name, String password, Model model) {
// 获取Subject
Subject subject = SecurityUtils.getSubject();
// 封装用户数据
UsernamePasswordToken token = new UsernamePasswordToken(name, password);
// 执行登录方法
try {
subject.login(token);
return "redirect:/index";
} catch (UnknownAccountException e) {
// 登录失败:用户名不存在
model.addAttribute("msg", "用户名不存在");
return "/login";
} catch (IncorrectCredentialsException e) {
// 登录失败:密码错误
model.addAttribute("msg", "密码错误");
return "/login";
}
}
@ApiOperation("登录成功")
@RequestMapping(value = "/index", method = RequestMethod.GET)
@ResponseBody
public Object index() {
Map map = new HashMap<>(16);
map.put("code", 100);
map.put("msg", "您登录成功");
return map;
}
@ApiOperation(value = "未授权提示")
@RequestMapping(value = "/noAuth", method = RequestMethod.GET)
@ResponseBody
public Object noAuth() {
Map map = new HashMap<>(16);
map.put("code", 200);
map.put("msg", "您没有该授权");
return map;
}
}
测试只需一个login.html的测试页面即可,其他跳转直接以json打印出来就好。
login.html登录页面使用了thymeleaf模板引擎。
login.html:
Login
登录
到这里,基本的拦截已经完成了。下一篇我们在用3DES加密为密码进行加密。
如果有什么需要改进的,还请多加指教。