springboot整合shiro安全框架
一、shiro简介
-
概述
apache shiro 是java的一个轻量级的安全框架,功能没有spring security全面,但对于一般的的项目已经足够。其最大的优点是易于上手,所以大多的公司都会用shiro.
Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等
-
组件
Subject:主体,可以是任何能与应用交互的 “用户”;
SecurityManager:相当于 SpringMVC 中的 DispatcherServlet;是 Shiro 的心脏;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。
Authenticator:认证器,负责主体认证的, Shiro 提供默认,但也可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
Realm:可以有 1 个或多个 Realm,用于获取安全实体的;可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等;由用户提供;注意:Shiro 不知道你的用户 / 权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm;
SessionManager:SessionManager管理Session 的生命周期;Shiro 抽象了一个自己的 Session 来管理主体与应用之间交互的数据;
SessionDAO:DAO 大家都用过,数据访问对象,用于会话的 CRUD,比如我们想把 Session 保存到数据库,那么可以实现自己的 SessionDAO,通过如 JDBC 写到数据库;比如想把 Session 放到 Memcached 中,可以实现自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 进行缓存,以提高性能;
CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能
Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于如密码加密 / 解密的。
二、使用示例
1.引入依赖
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!--简化bean-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
2.编写实体类
-
user.java(用户实体类)
package top.xtslife.security.bean; import lombok.Data; import java.io.Serializable; import java.util.Set; /** * @Author 小涛 * @Create 2019-08-20 11:12 */ @Data public class User implements Serializable { private String id; private String userName; private String password; /** * 用户对应的角色集合 */ private Set<Role> roles; public User() { } public User(String id, String userName, String password, Set<Role> roles) { this.id = id; this.userName = userName; this.password = password; this.roles = roles; } }
-
Role.java(角色对应实体类)
package top.xtslife.security.bean; import lombok.Data; import java.io.Serializable; import java.util.Set; /** * @Author 小涛 * @Create 2019-08-20 11:14 */ @Data public class Role implements Serializable { private String id; private String roleName; /** * 角色对应权限集合 */ private Set<Permissions> permissions; public Role() { } public Role(String id, String roleName, Set<Permissions> permissions) { this.id = id; this.roleName = roleName; this.permissions = permissions; } }
-
Permissions.java(权限对应实体类)
package top.xtslife.security.bean; import lombok.Data; import java.io.Serializable; /** * @Author 小涛 * @Create 2019-08-20 11:14 */ @Data public class Permissions implements Serializable { private String id; private String permissionsName; public Permissions() { } public Permissions(String id, String permissionsName) { this.id = id; this.permissionsName = permissionsName; } }
-
编写LoginService
package top.xtslife.security.service; import top.xtslife.security.bean.User; /** * @Author 小涛 * @Create 2019/8/20 11:31 */ public interface LoginService { User getUserByName(String getMapByName); }
-
编写实现类
package top.xtslife.security.service.impl; /** * @Author 小涛 * @Create 2019-08-20 11:30 */ import org.springframework.stereotype.Service; import top.xtslife.security.bean.Permissions; import top.xtslife.security.bean.Role; import top.xtslife.security.bean.User; import top.xtslife.security.service.LoginService; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; @Service public class LoginServiceImpl implements LoginService { @Override public User getUserByName(String getMapByName) { //模拟数据库查询,正常情况此处是从数据库或者缓存查询。 return getMapByName(getMapByName); } /** * 模拟数据库查询 * @param userName * @return */ private User getMapByName(String userName){ //共添加两个用户,两个用户都是admin一个角色, //wsl有query和add权限,zhangsan只有一个query权限 Permissions permissions1 = new Permissions("1","query"); Permissions permissions2 = new Permissions("2","add"); Set<Permissions> permissionsSet = new HashSet<>(); permissionsSet.add(permissions1); permissionsSet.add(permissions2); Role role = new Role("1","admin",permissionsSet); Set<Role> roleSet = new HashSet<>(); roleSet.add(role); User user = new User("1","wsl","123456",roleSet); Map<String ,User> map = new HashMap<>(); map.put(user.getUserName(), user); Permissions permissions3 = new Permissions("3","query"); Set<Permissions> permissionsSet1 = new HashSet<>(); permissionsSet1.add(permissions3); Role role1 = new Role("2","user",permissionsSet1); Set<Role> roleSet1 = new HashSet<>(); roleSet1.add(role1); User user1 = new User("2","zhangsan","123456",roleSet1); map.put(user1.getUserName(), user1); return map.get(userName); } }
-
自定义 Realm:用于查询用户的角色和权限信息并保存到权限管理器
package top.xtslife.security.realm; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.beans.factory.annotation.Autowired; import top.xtslife.security.bean.Permissions; import top.xtslife.security.bean.Role; import top.xtslife.security.bean.User; import top.xtslife.security.service.LoginService; /** * @Author 小涛 * @Create 2019-08-20 11:45 */ public class CustomRealm extends AuthorizingRealm { @Autowired private LoginService loginService; @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //获取登录用户名 String name = (String) principalCollection.getPrimaryPrincipal(); //根据用户名去数据库查询用户信息 User user = loginService.getUserByName(name); //添加角色和权限 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); for (Role role : user.getRoles()) { //添加角色 simpleAuthorizationInfo.addRole(role.getRoleName()); //添加权限 for (Permissions permissions : role.getPermissions()) { simpleAuthorizationInfo.addStringPermission(permissions.getPermissionsName()); } } return simpleAuthorizationInfo; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //加这一步的目的是在Post请求的时候会先进认证,然后在到请求 if (authenticationToken.getPrincipal() == null) { return null; } //获取用户信息 String name = authenticationToken.getPrincipal().toString(); User user = loginService.getUserByName(name); if (user == null) { //这里返回后会报出对应异常 return null; } else { //这里验证authenticationToken和simpleAuthenticationInfo的信息 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, user.getPassword().toString(), getName()); return simpleAuthenticationInfo; } } }
-
写javaconfig:把 CustomRealm 和 SecurityManager 等加入到 spring 容器
package top.xtslife.security.config; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import top.xtslife.security.realm.CustomRealm; import java.util.HashMap; import java.util.Map; @Configuration public class ShiroConfig { //不加这个注解不生效,具体不详 @Bean @ConditionalOnMissingBean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator(); defaultAAP.setProxyTargetClass(true); return defaultAAP; } //将自己的验证方式加入容器 @Bean public CustomRealm myShiroRealm() { CustomRealm customRealm = new CustomRealm(); return customRealm; } //权限管理,配置主要是Realm的管理认证 @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); return securityManager; } //Filter工厂,设置对应的过滤条件和跳转条件 @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String, String> map = new HashMap<>(); //登出 map.put("/logout", "logout"); //对所有用户认证 map.put("/**", "authc"); //登录 shiroFilterFactoryBean.setLoginUrl("/login"); //首页 shiroFilterFactoryBean.setSuccessUrl("/index"); //错误页面,认证不通过跳转 shiroFilterFactoryBean.setUnauthorizedUrl("/error"); shiroFilterFactoryBean.setFilterChainDefinitionMap(map); return shiroFilterFactoryBean; } //加入注解的使用,不加入这个注解不生效 @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } }
-
编写controller
package top.xtslife.security.controller; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationException; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresRoles; import org.apache.shiro.subject.Subject; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import top.xtslife.security.bean.User; @RestController @Api(tags ="LoginController",description = "用户登录管理") public class LoginController { @RequestMapping("/login") @ApiOperation("用户登录") public String login(User user) { //添加用户认证信息 Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken( user.getUserName(), user.getPassword() ); try { //进行验证,这里可以捕获异常,然后返回对应信息 subject.login(usernamePasswordToken); // subject.checkRole("admin"); // subject.checkPermissions("query", "add"); } catch (AuthenticationException e) { e.printStackTrace(); return "账号或密码错误!"; } catch (AuthorizationException e) { e.printStackTrace(); return "没有权限"; } return "login success"; } //注解验角色和权限 @RequiresRoles("admin") @RequiresPermissions("add") @RequestMapping("/index") @ApiOperation("通过验证访问主页面") public String index() { return "index!"; } }
-
编写异常处理
package top.xtslife.security.component; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.authz.AuthorizationException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; @ControllerAdvice @Slf4j public class MyExceptionHandler { @ExceptionHandler @ResponseBody public String ErrorHandler(AuthorizationException e) { log.error("没有通过权限验证!", e); return "没有通过权限验证!"; } }