1. 程式人生 > >使用SpringBoot2.0搭建企業級應用開發框架(七)整合Shiro

使用SpringBoot2.0搭建企業級應用開發框架(七)整合Shiro

  • 準備

首先建立使用者許可權表

//使用者表
CREATE TABLE `sys_user` (
  `id` varchar(32) NOT NULL COMMENT 'id',
  `username` varchar(64) DEFAULT NULL COMMENT '使用者名稱',
  `password` varchar(64) DEFAULT NULL COMMENT '密碼',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='使用者表';
INSERT INTO `demo`.`sys_user` (`id`, `username`, `password`) VALUES ('1', 'admin', '123456');

//角色表
CREATE TABLE `sys_role` (
  `id` varchar(32) NOT NULL COMMENT 'id',
  `role_name` varchar(32) DEFAULT NULL COMMENT '角色名稱',
  `role_desc` varchar(32) DEFAULT NULL COMMENT '描述',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色表';
INSERT INTO `demo`.`sys_role` (`id`, `role_name`, `role_desc`) VALUES ('1', 'ROLE_ADMIN', '管理員');

//使用者-角色表
CREATE TABLE `sys_user_role` (
  `id` varchar(32) NOT NULL COMMENT 'id',
  `user_id` varchar(32) DEFAULT NULL COMMENT '使用者id',
  `role_id` varchar(32) DEFAULT NULL COMMENT '角色id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='使用者角色表';
INSERT INTO `demo`.`sys_user_role` (`id`, `user_id`, `role_id`) VALUES ('1', '1', '1');

 然後用之前說的generator生成實體

  • 整合

首先新增Shiro的依賴

<!--shiro-->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-web-starter</artifactId>
    <version>1.4.0</version>
</dependency>

然後建立自定義Realm

public class MyRealm extends AuthorizingRealm {

    @Autowired
    private SysUserMapper sysUserMapper;

    //認證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
            throws AuthenticationException {
        //獲得當前使用者的使用者名稱
        String username = (String) authenticationToken.getPrincipal();

        //從資料庫中根據使用者名稱查詢使用者
        SysUser user = sysUserMapper.findByUserName(username);
        if (user == null) {
            log.info("沒有使用者名稱為{}的使用者",username);
            throw new UnknownAccountException("沒有在本系統中找到對應的使用者資訊");
        }

        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(),getName());
        return info;
    }

    //授權
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //從憑證中獲得使用者名稱
        String username = (String) SecurityUtils.getSubject().getPrincipal();
        //根據使用者名稱查詢使用者物件
        SysUser user = sysUserMapper.findByUserName(username);
        //查詢使用者擁有的角色
        List<SysRole> roleList = user.getRoles();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        log.debug("使用者{}擁有以下角色:");
        for (SysRole role : roleList) {
            //賦予使用者角色
            info.addStringPermission(role.getRoleName());
        }
        return info;
    }
}

建立config類

@Configuration
public class ShiroConfiguration {

    @Bean
    public MyRealm realm(){
        return new MyRealm();
    }

    //使用shiro-spring-boot-starter 1.4時,返回型別是SecurityManager會報錯,直接引用shiro-spring則不報錯
    @Bean
    public DefaultWebSecurityManager securityManager(MyRealm realm){
        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        return securityManager;
    }

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean  = new ShiroFilterFactoryBean();
        // 必須設定 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // 如果不設定預設會自動尋找Web工程根目錄下的"/login.jsp"頁面
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登入成功後要跳轉的連結
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //未授權介面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");

        //攔截器.
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
        //配置退出過濾器,其中的具體的退出程式碼Shiro已經替我們實現了
        filterChainDefinitionMap.put("/logout", "logout");
        // 過濾鏈定義,從上向下順序執行,一般將 /**放在最為下邊 :這是一個坑呢,一不小心程式碼就不好使了;
        // authc:所有url都必須認證通過才可以訪問; anon:所有url都都可以匿名訪問
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/login.html", "anon");
        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;
    }
}

建立登入測試

@Controller
public class ShiroController {

    @GetMapping("/login")
    public String login(){
        return "redirect:login.html";
    }

    @PostMapping("/login")
    public String login(String username, String password){
        Subject user = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        String returnUrl = "redirect:index.html";
        String a = "1";
        try {
            //shiro幫我們匹配密碼什麼的,我們只需要把東西傳給它,它會根據我們在UserRealm裡認證方法設定的來驗證
            user.login(token);
        } catch (UnknownAccountException e) {
            //賬號不存在
            returnUrl = "redirect:login.html?error=0";
            //賬號不存在和下面密碼錯誤一般都合併為一個賬號或密碼錯誤,這樣可以增加暴力破解難度
        } catch (DisabledAccountException e) {
            //賬號未啟用
            returnUrl = "redirect:login.html?error=1";
        } catch (IncorrectCredentialsException e) {
            //密碼錯誤
            returnUrl = "redirect:login.html?error=2";
        } catch (Throwable e) {
            //未知錯誤
            returnUrl = "redirect:login.html?error=3";
        } finally {
            return returnUrl;
        }
    }
}

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title1</title>
</head>
<body>
    <form method="post" action="/login">
        使用者名稱:<input name="username" /><br />
        密碼:<input name="password" /><br />
        <input type="submit" value="登入" />
    </form>
</body>
</html>

整合中的坑

1、config類中的securityManager()返回型別

如果jar包用spring-shiro,則返回SecurityManager即可,如果用shiro-spring-boot-starter 1.4,則需要返回DefaultWebSecurityManager,否則會報錯

2、多環境配置

多環境配置中需要手動定義resuources目錄下載入的檔案,所以不要忘記將html加入進去

3、頁面跳轉問題

如果使用return "xx.html"方式的話,在登陸時,由於提交表單是post,而訪問xx.html是get方法,所以會報錯,需要使用redirect或者forward