只須要6個步驟,springboot集成shiro,並完成登陸

小Hub領讀:

導入jar包,配置yml參數,編寫ShiroConfig定義DefaultWebSecurityManager,重寫Realm,編寫controller,編寫頁面,一鼓作氣。搞定,是個高手~html


上面一篇文章中,咱們已經知道了shiro的認證與受權過程,這也是shiro裏面最核心經常使用的基礎功能。如今咱們把shiro集成到咱們的項目中,開始搭建一個有認證和權限體系的項目,好比用戶中心須要登陸以後才能訪問等!前端

一、極簡入門,Shiro的認證與受權流程解析git

集成Shiro

根據官方文檔:
https://shiro.apache.org/spring-boot.htmlgithub

第一步:集成導入jar包:web

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

有些同窗還在用shiro-spring的jar包,可是集成的配置就相對多一點,因此能夠直接使用starter包更加方便。面試

第二步:寫好配置,官方給咱們提供的屬性參數,以及一些默認值,若是不符合咱們的需求,能夠自行改動哈。redis

從配置上就能夠看出,shiro的註解功能,rememberMe等功能已近自動集成進來了。因此starter包使用起來仍是很是簡單的,只須要熟悉shiro的流程,從0開發不在話下哈。spring

  • application.yml
shiro:
  web:
    enabled: true
  loginUrl: /login
spring:
  freemarker:
    suffix: .ftl # 注意新版本後綴是 .ftlh
    template-loader-path: classpath:/templates/
    settings:
      classic_compatible: true #處理空值

上面的配置,我就改了一下登陸的url,其餘都是使用默認的,做爲咱們最簡單的測試,相信大家。apache

第三步:配置shiro的securityManager和自定義realm。由於realm負責咱們的認證與受權,因此是必須的,自定義的realm必需要交給securityManager管理,因此這兩個類須要重寫。而後還有一些資源的權限說明,因此通常須要定義ShiroFilterChainDefinition,因此有3個類咱們常寫的:緩存

  • AuthorizingRealm
  • DefaultWebSecurityManager shiro的核心管理器
  • ShiroFilterChainDefinition 過濾器鏈配置
@Configuration
public class ShiroConfig {

    @Bean
    AccountRealm accountRealm() {
        return new AccountRealm();
    }

    @Bean
    public DefaultWebSecurityManager securityManager(AccountRealm accountRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(accountRealm);
        return securityManager;
    }

    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();

        // logged in users with the 'admin' role
        chainDefinition.addPathDefinition("/admin/**", "authc, roles[admin]");

        // logged in users with the 'document:read' permission
        chainDefinition.addPathDefinition("/docs/**", "authc, perms[document:read]");

        chainDefinition.addPathDefinition("/login", "anon");
        chainDefinition.addPathDefinition("/doLogin", "anon");

        // all other paths require a logged in user
        chainDefinition.addPathDefinition("/**", "authc");
        return chainDefinition;
    }
}

上面說到ShiroFilterChainDefinition是定義過濾器配置的,啥意思呢,咱們來看看其中一句:

chainDefinition.addPathDefinition("/admin/**", "authc, roles[admin]");

這一句代碼意思是說:訪問/admin/**開頭的連接,都須要已經完成登陸認證authc、而且擁有admin角色權限才能訪問。

你能夠看到key-value是連接-過濾器的組合,過濾器能夠同時多個。那麼authc、role、perms、anon究竟是哪來的呢?有啥特殊意義?是啥攔截器?

咱們來看下這個說明文檔:

能夠看到,其實每一個簡寫單詞,都是一個過濾器的名稱。好比authc表明這FormAuthenticationFilter。每一個過濾器具體是啥用的?咱們看幾個經常使用的吧:

  • authc 基於表單的攔截器,沒有登陸會跳到相應的登陸頁面登陸
  • user 用戶攔截器,用戶已經身份驗證 / 記住我登陸的均可
  • anon 匿名攔截器,即不須要登陸便可訪問
  • roles 角色受權攔截器,驗證用戶是否擁有全部角色
  • perms 權限受權攔截器,驗證用戶是否擁有全部權限

第四步:第ok,根據需求項目的資源制定項目過濾器鏈ShiroFilterChainDefinition。咱們再回到AccountRealm這個類。咱們以前說過,認證受權的過程,咱們是在Realm裏面完成的。因此咱們須要繼承Realm,並實現兩個方法。

可是這裏須要注意,咱們通常不直接繼承Realm,能夠看看Realm接口:

  • org.apache.shiro.realm.Realm
public interface Realm {
    String getName();

    boolean supports(AuthenticationToken token);

    AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;

}

而從上一篇文章中,咱們分析的認證受權的源碼過程時候,你會看到,認證和受權分別調用的realm是AuthenticatingRealmAuthorizingRealm。說明源碼裏面已經通過了一些封裝,因此咱們就不能再直接繼承Realm,那麼AuthenticatingRealmAuthorizingRealm咱們繼承哪一個呢?咱們發現AuthorizingRealm是繼承AuthenticatingRealm的,因此在重寫realm的時候,咱們只須要集成超類AuthorizingRealm便可。

public abstract class AuthorizingRealm extends AuthenticatingRealm

因此,結合了受權與驗證,還有緩存功能,咱們自定義Realm的時候繼承AuthorizingRealm便可。

  • com.markerhub.shiro.AccountRealm
public class AccountRealm extends AuthorizingRealm {

    @Autowired
    UserService userService;

    /**
     * 受權方法
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        AccountProfile principal = (AccountProfile) principalCollection.getPrimaryPrincipal();

        // 硬編碼(賦予用戶權限或角色)
        if(principal.getUsername().equals("MarkerHub")){
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            info.addRole("admin");
            return info;
        }

        return null;
    }

    /**
     * 認證方法
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        AccountProfile profile = userService.login(token.getUsername(), String.valueOf(token.getPassword()));
        // 把用戶信息存到session中,方便前端展現
        SecurityUtils.getSubject().getSession().setAttribute("profile", profile);

        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(profile, token.getCredentials(), getName());
        return info;
    }
}
  • com.markerhub.service.impl.UserServiceImpl
@Service
public class UserServiceImpl implements UserService {

    @Override
    public AccountProfile login(String username, String password) {

        //TODO 查庫,而後匹配密碼是否正確!

        if(!"MarkerHub".equals(username)) {
            // 拋出shiro異常,方便通知用戶登陸錯誤信息
            throw new UnknownAccountException("用戶不存在");
        }
        if(!"111111".equals(password)) {
            throw new IncorrectCredentialsException("密碼錯誤");
        }

        AccountProfile profile = new AccountProfile();
        profile.setId(1L);
        profile.setUsername("MarkerHub");
        profile.setSign("歡迎關注公衆號MarkerHub哈");

        return profile;
    }
}

上面代碼中,我login方法直接給出了帳號MarkerHub,並賦予了角色admin。

第五步:ok,準備動做已經熱身完畢,接下來咱們去編寫登陸、退出接口,以及咱們的界面:

  • com.markerhub.controller.IndexController
@Controller
public class IndexController {

    @Autowired
    HttpServletRequest req;

    @RequestMapping({"/", "/index"})
    public String index() {
        System.out.println("已登陸,正在訪問!!");
        return "index";
    }

    @GetMapping("/login")
    public String login() {
        return "login";
    }

    /**
     * 登陸
     */
    @PostMapping("/doLogin")
    public String doLogin(String username, String password) {

        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            SecurityUtils.getSubject().login(token);

        } catch (AuthenticationException e) {
            if (e instanceof UnknownAccountException) {
                req.setAttribute("errorMess", "用戶不存在");
            } else if (e instanceof LockedAccountException) {
                req.setAttribute("errorMess", "用戶被禁用");
            } else if (e instanceof IncorrectCredentialsException) {
                req.setAttribute("errorMess", "密碼錯誤");
            } else {
                req.setAttribute("errorMess", "用戶認證失敗");
            }
            return "/login";
        }
        return "redirect:/";
    }


    /**
     * 退出登陸
     */
    @GetMapping("/logout")
    public String logout() {
        SecurityUtils.getSubject().logout();
        return "redirect:/login";
    }

}

第六步:登陸頁面:

  • templates/login.ftl
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8"/>
    <title>MarkerHub 登陸</title>
</head>
<body>
    <h1>用戶登陸</h1>
    <h3>歡迎關注公衆號:MarkerHub</h3>

    <form method="post" action="/doLogin">
        username: <input name="username" type="text">
        password: <input name="password" type="password">
        <input type="submit" name="提交">
    </form>

    <div style="color: red;">${errorMess}</div>
</body>
</html>

登陸成功頁面:

  • templates/index.ftl
<h1>登陸成功:${profile.username}</h1>
<h3>${profile.sign}</h3>

<div><a href="/logout">退出</a></div>

ok,代碼咱們已經編寫完成,接下來,咱們運行項目,而後訪問首頁,將自行跳轉到登陸頁面,而後輸入帳號密碼以後,咱們能夠看到完成登陸!

登陸界面:

登陸成功頁面:

結束語

好了,今天作了一個極簡的登陸註冊功能,介紹了一下shiro的基本整合步驟。流程仍是挺簡單的哈哈,不知道你看懂了沒。

而在一些負載均衡的場景中,咱們的會話信息是須要共享的,因此shiro通常會和redis整合在一塊兒,你知道怎麼整合嗎?咱們明天再聊哈,記得來哦,MarkerHub天天發文時間19點20分。

附:demo git源碼地址https://github.com/MarkerHub/...

推薦閱讀:

分享一套SpringBoot開發博客系統源碼,以及完整開發文檔!速度保存!

Github上最值得學習的100個Java開源項目,涵蓋各類技術棧!

2020年最新的常問企業面試題大全以及答案

相關文章
相關標籤/搜索