Shiro解决了什么问题?
互联网无非就是一些用户C想要使用一些服务S的资源去完成某件事,S的资源不能说给谁用就给谁用,因此产生了权限的概念,即C必须有权限才能操作S的资源。S如何确定C就是C呢?因此又产生了身份验证的概念。一个Authorization一个Authentication就是Shiro解决的最重要的两个问题,其他的功能都是给Shiro打辅助的,比如Session管理,加密处理,记住我等。
Shiro是什么?
把Shiro想象成一家安全公司
公司给服务端提供的服务是:服务端把自己维护的权限啊、用户啊、角色啊什么的信息通过接口提供给Shiro,shiro就可以帮服务端处理用户权限角色等的安全认证和授权等工作
公司给客户提供的服务是:客户可以是任何外来的东西,但是想要访问服务端提供的要求权限验证等资源,就必须先经过shiro这层把关,shiro会对客户进行安全认证和授权等工作
Shiro重要概念有哪些?
step1
创建5张表,分别记录用户、角色、权限、用户角色关系、角色权限关系。
建表很随意的,字段随便起名字,哪些字段也很随意,甚至有些表可以不存在的(比如权限表)。比如你可以简单的给user表一个id一个name,给role表一个id一个name。
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(32) NOT NULL,
`name` varchar(60) DEFAULT NULL,
`mob` varchar(60) DEFAULT NULL,
`email` varchar(150) DEFAULT NULL,
`valid` int(2) DEFAULT NULL,
`pticket` varchar(200) DEFAULT NULL,
`role_id` int(32) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`uid` int(32) NOT NULL,
`role_id` int(32) NOT NULL,
PRIMARY KEY (`uid`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` int(32) NOT NULL,
`description` varchar(255) DEFAULT NULL,
`role` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission` (
`permission_id` int(32) NOT NULL,
`role_id` int(32) NOT NULL,
PRIMARY KEY (`permission_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
`id` int(32) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`description` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
step2
设计上面表的dao层和service层,只要提供根据user的id或者某个属性查询到所有的角色及权限信息就足够了
比如我写的接口,dao用mybatisGenerator生成,service自己完成两个根据用户id获取角色和权限信息的接口。
@Service
public class UserService {
@Autowired
private UserRoleDAO userRoleDAO;
@Autowired
private UserDAO userDAO;
@Autowired
private RoleDAO roleDAO;
@Autowired
private PermissionDAO permissionDAO;
@Autowired
private RolePermissionDAO rolePermissionDAO;
//根据用户id查询所有的角色信息
public List findRoles(Integer id) {
UserRoleExample example = new UserRoleExample();
example.createCriteria().andUidEqualTo(id);
List keyList = userRoleDAO.selectByExample(example);
List roleIdList = new ArrayList<>(keyList.size());
for (UserRoleKey userRoleKey : keyList) {
roleIdList.add(userRoleKey.getRoleId());
}
RoleExample roleExample = new RoleExample();
roleExample.createCriteria().andIdIn(roleIdList);
return roleDAO.selectByExample(roleExample);
}
//根据用户的id查询所有权限信息
public List findPermissions(Integer id) {
List roles = findRoles(id);
List roleIds = new ArrayList<>(roles.size());
for (Role role : roles) {
roleIds.add(role.getId());
}
RolePermissionExample example = new RolePermissionExample();
example.createCriteria().andRoleIdIn(roleIds);
List keyList = rolePermissionDAO.selectByExample(example);
List permissionIdList = new ArrayList<>(keyList.size());
for (RolePermissionKey rolePermissionKey : keyList) {
permissionIdList.add(rolePermissionKey.getPermissionId());
}
PermissionExample permissionExample = new PermissionExample();
if (permissionIdList.size() != 0) {
permissionExample.createCriteria().andIdIn(permissionIdList);
return permissionDAO.selectByExample(permissionExample);
}
return new ArrayList<>();
}
public User findUserById(String uId) {
return userDAO.selectByPrimaryKey(Integer.valueOf(uId));
}
@Transactional
public int assignDefaultUserRolePermission(User user) {
int success1 = userDAO.insert(user);
UserRoleKey userRoleKey = new UserRoleKey();
userRoleKey.setUid(user.getId());
userRoleKey.setRoleId(2);
int success2 = userRoleDAO.insert(userRoleKey);
return success1 + success2;
}
}
step3
把获取角色和权限信息的userService的两个接口提供给Shiro,让Shiro有法可依。
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
//shiro的权限信息配置
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
String uid = (String) principals.getPrimaryPrincipal();
List roles = userService.findRoles(Integer.valueOf(uid));
Set roleNames = new HashSet<>(roles.size());
for (Role role : roles) {
roleNames.add(role.getRole());
}
//此处把当前subject对应的所有角色信息交给shiro,调用hasRole的时候就根据这些role信息判断
authorizationInfo.setRoles(roleNames);
List permissions = userService.findPermissions(Integer.valueOf(uid));
Set permissionNames = new HashSet<>(permissions.size());
for (Permission permission : permissions) {
permissionNames.add(permission.getName());
}
//此处把当前subject对应的权限信息交给shiro,当调用hasPermission的时候就会根据这些信息判断
authorizationInfo.setStringPermissions(permissionNames);
return authorizationInfo;
}
//根据token获取认证信息authenticationInfo
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
/**这里为什么是String类型呢?其实要根据Subject.login(token)时候的token来的,你token定义成的pricipal是啥,这里get的时候就是啥。比如我
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken idEmail = new UsernamePasswordToken(String.valueOf(user.getId()), user.getEmail());
try {
idEmail.setRememberMe(true);
subject.login(idEmail);
}
**/
String uId = (String) token.getPrincipal();
User user = userService.findUserById(uId);
if (user == null) {
return null;
}
//SimpleAuthenticationInfo还有其他构造方法,比如密码加密算法等,感兴趣可以自己看
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
uId, //表示凭证,可以随便设,跟token里的一致就行
user.getEmail(), //表示密钥如密码,你可以自己随便设,跟token里的一致就行
getName()
);
//authenticationInfo信息交个shiro,调用login的时候会自动比较这里的token和authenticationInfo
return authenticationInfo;
}
}
step4
对shiro进行一些配置,如登陆路径、权限验证、密码匹配等等.
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//拦截器.
Map filterChainDefinitionMap = new LinkedHashMap();
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "anon");
filterChainDefinitionMap.put("/afterlogout", "anon");
//:这是一个坑呢,一不小心代码就不好使了;
//
filterChainDefinitionMap.put("/static/**", "anon");
// filterChainDefinitionMap.put("/html/**","anon");
filterChainDefinitionMap.put("/afterlogin", "anon");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public Realm myShiroRealm() {
UserRealm myShiroRealm = new UserRealm();
return myShiroRealm;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
return securityManager;
}
@Bean(name="lifecycleBeanPostProcessor")
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
*
* @return
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
}
step5
完成以上配置就可以正常些登录登出接口,以及权限验证接口了。我这里边是利用了类似于openid的统一登录认证接口,然后写了几个登录接口。注意这里的Session,一定要用SecurityUtils.getSubject.getSession,不然会有坑。
@RestController
public class LoginController {
@Autowired
private LoginService loginService;
@RequestMapping("/login")
public void login(HttpServletResponse response) {
response.setStatus(302);
try {
response.sendRedirect(OPSConstants.OPS_URL_WITH_RETURN);
} catch (IOException e) {
}
}
@RequestMapping("/afterlogin")
public void recTicket(String ticket, HttpServletRequest request, HttpServletResponse response) {
Map paramMap = new HashMap<>(1);
paramMap.put("ticket", ticket);
String result = HttpHandler.getInstance().usingGetMethod(OPSConstants.OPS_URL_WITH_TICKET, paramMap, null);
User user = JSON.parseObject(result, User.class);
loginService.registerOrLogin(user);
HttpSession session = request.getSession();
session.setMaxInactiveInterval(1000 * 60 * 60);
session.setAttribute(session.getId(), user);
try {
response.sendRedirect("/html/index.html");
} catch (IOException e) {
}
}
@RequestMapping(value = "/logout")
public void logout(HttpServletRequest request, HttpServletResponse response) {
HttpSession session = request.getSession();
User user = (User) session.getAttribute(session.getId());
String pticket = user.getPticket();
String url = OPSConstants.OPS_URL_LOGOUT + "&pticket=" + pticket;
response.setStatus(302);
try {
response.sendRedirect(url);
} catch (IOException e) {
}
}
@RequestMapping(value = "/afterlogout")
public void afterLogout(HttpServletResponse response) {
//这里一定要使用shiro退出方式,否则session失效
SecurityUtils.getSubject().logout();
try {
response.sendRedirect(OPSConstants.OPS_URL_WITH_RETURN);
} catch (IOException e) {
}
}
}
无权处理:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = UnauthorizedException.class)
@ResponseBody
public ResponseBO jsonExceptionHandler(HttpServletRequest req, Exception e) {
return new ResponseBO(403, "权限不足!");
}
// @ExceptionHandler(value = UnauthorizedException.class)
// public ModelAndView businessExceptionHandler(){
// ModelAndView mav = new ModelAndView();
// mav.setStatus(HttpStatus.UNAUTHORIZED);
// mav.addObject("message", e.getMessage());
// mav.setViewName("403");
// return mav;
// }
}
shiro虽然轻量,但是坑还是很多的,官方文档和网上的博客对初学者并不友好。学习的方法是git上找一个能通的项目,然后直接ctrl+左键查看源码就行,或者debug的时候打断点,查看那些配置的方法及传的参数。