zoukankan      html  css  js  c++  java
  • Spring Boot的安全之旅(一)

    使用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";
        }
    
    }
  • 相关阅读:
    如何根据select选择的值反查option的属性
    如何让数据库的某张表在记录达到100条的时候自动删除记录
    一个程序员如何发表演讲或者答辩?
    适合程序员演讲的定场诗
    字符串与二进制之间的相互转化
    百鸡百钱问题
    如何把he_llo wo_rld 变成 HeLlo WoRld
    SpringCloud(三)Eureka注册中心实现高可用
    SpringCloud(二)注册服务提供者搭建
    SpringCloud (一)Eureka注册中心搭建
  • 原文地址:https://www.cnblogs.com/xc-xinxue/p/12417146.html
Copyright © 2011-2022 走看看