使用Shiro安全管理
1.先进行场景及数据库介绍
就是分析需求。可以参考《Spring Boot实战之旅》。
2.引入相关依赖
<!--shiro--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency>
3.实体类及数据操作层
首先要分析出表和表之间的关系,user表和role表的关系是多对多,role表和menu表的关系也是多对多。
@Entity public class User implements Serializable { @Id @GeneratedValue private Integer userId; private String userName; private String passWord; @ManyToMany(fetch= FetchType.EAGER) @JoinTable(name = "UserRole", joinColumns = { @JoinColumn(name = "userId") }, inverseJoinColumns ={@JoinColumn(name = "roleId") }) private List<Role> roleList; //setter getter } @Entity public class Role implements Serializable { @Id @GeneratedValue private Integer roleId; private String roleName; @ManyToMany(fetch= FetchType.EAGER) @JoinTable(name="RoleMenu",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="menuId")}) private List<Menu> menuList; @ManyToMany @JoinTable(name="UserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="userId")}) private List<User> userList; //setter getter } @Entity public class Menu implements Serializable { @Id @GeneratedValue private Integer menuId; private String menuName; @ManyToMany @JoinTable(name="RoleMenu",joinColumns={@JoinColumn(name="menuId")},inverseJoinColumns={@JoinColumn(name="roleId")}) private List<Role> roleList; //setter getter }
创建一个JPA数据操作层,里面加入一个根据用户名查询用户的方法:
public interface UserRepository extends JpaRepository<User,Long> { User findByUserName(String username); }
4.配置shiro
创建一个 ShiroConfig,然后创建一个 shiroFilter方法。在 Shiro使用认证和授权时,其实都是通过 ShiroFilterFactoryBean设置一些Shiro的拦截器进行的,拦截器会以 LinkedHashMap的形式存储需要拦截的资源及链接,并且会按照顺序执行,其中键为拦截的资源或链接,值为拦截的形式(比如 authc:所有URL都必须认证通过才可以访问,anon:所有URL都可以匿名访问),在拦截的过程中可以使用通配符,比如/**为拦截所有,所以一般放在最下面。同时,可以通过ShiroFilterFactoryBean设置登录链接、未授权链接、登录成功跳转页等,这里设置的 shiroFilter方法内容如以下代码所示:
@Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); //shiro拦截器 Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>(); //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问--> //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 --> // 配置不被拦截的资源及链接 filterChainDefinitionMap.put("/static/**", "anon"); // 退出过滤器 filterChainDefinitionMap.put("/logout", "logout"); //配置需要认证权限的 filterChainDefinitionMap.put("/**", "authc"); // 默认寻找登录链接 shiroFilterFactoryBean.setLoginUrl("/login"); // 登录成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl("/index"); //未授权的跳转链接 shiroFilterFactoryBean.setUnauthorizedUrl("/401"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } //自定义身份认证Realm(包含用户名密码校验,权限校验等) @Bean public MyShiroRealm myShiroRealm(){ MyShiroRealm myShiroRealm = new MyShiroRealm(); return myShiroRealm; } @Bean public SecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); return securityManager; } //开启shiro aop注解支持,不开启的话权限验证就会失效 @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } //处理异常,当用户没有权限时设置跳转到401页面 @Bean(name="simpleMappingExceptionResolver") public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() { SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver(); Properties mappings = new Properties(); //数据库异常处理 mappings.setProperty("DatabaseException", "databaseError"); //未经过认证 mappings.setProperty("UnauthorizedException","401"); // None by default simpleMappingExceptionResolver.setExceptionMappings(mappings); // No default simpleMappingExceptionResolver.setDefaultErrorView("error"); // Default is "exception" simpleMappingExceptionResolver.setExceptionAttribute("ex"); return simpleMappingExceptionResolver; } }
MyShiroRealm类代码如下:
@Configuration public class MyShiroRealm extends AuthorizingRealm { @Resource private UserRepository userRepository; //授权方法,主要用于获取角色的菜单权限 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); User userInfo = (User)principals.getPrimaryPrincipal(); for(Role role:userInfo.getRoleList()){ authorizationInfo.addRole(role.getRoleName()); for(Menu menu:role.getMenuList()){ authorizationInfo.addStringPermission(menu.getMenuName()); } } return authorizationInfo; } //认证方法,主要用于校验用户名和密码 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //获得当前用户的用户名 String username = (String)token.getPrincipal(); //根据用户名查询用户 User user = userRepository.findByUserName(username); if(user == null){ return null; } //校验用户名密码是否正确 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( user, user.getPassWord(), getName() ); return authenticationInfo; } }
注意既然是配置类,都要在类上加上@Configuration,这种默认操作一开始就加后面容易忘。
5.静态页面
这就不多说了。测试而已,很简单。
6.新建controller
@Controller public class ShiroController { @GetMapping({"/","/index"}) public String index(){ return"index"; } @GetMapping("/401") public String unauthorizedRole(){ return "401"; } @GetMapping("/delete") @RequiresRoles("admin") public String delete(){ return "delete"; } @GetMapping("/select") @RequiresPermissions("select") public String select(){ return "select"; } @RequestMapping("/login") public String login(HttpServletRequest request, Map<String, Object> map){ // 如果登录失败的话,那么就从HttpServletRequest中获取shiro处理的异常信息,获取shiroLoginFailure就是shiro异常类的全名。 String exception = (String) request.getAttribute("shiroLoginFailure"); String msg = ""; //根据异常判断错误类型 if (exception != null) { if (UnknownAccountException.class.getName().equals(exception)) { msg = "用户名不存在!"; } else if (IncorrectCredentialsException.class.getName().equals(exception)) { msg = "密码错误!"; } else { msg = exception; } } map.put("msg", msg); return "/login"; } @GetMapping("/logout") public String logout(){ return "/login"; } }