springBoot探索(2)——構建手腳架

pc859107393發表於2017-09-22

已經有一個多月沒有更新這個系列的文章了。本期目標是完成基礎的手腳架。

號外

本專案github倉庫:github.com/pc859107393…

本專案國內碼雲倉庫:git.oschina.net/859107393/M…

本系列為連載文章。當然如果你沒有spring基礎,建議你先看看我的java手把手教程

當然我的簡書訪問速度更快

有興趣交流springboot進行快速開發的同學可以加一下下面的企鵝群。

行走的java全棧
行走的java全棧

正文開始

首先我們需要簡單的看看我們專案需要的支援:

  • 快速部署:spring-boot-devtools
  • 資料庫相關:

    • Mybatis
    • Mybatis-plus(常規crud、自帶分頁)
    • Druid
    • 資料庫連結
  • 核心依賴:spring

  • web處理:springMvc
  • 許可權和鑑定:Shiro
  • 網路通訊:okhttp
  • json解析:gson、fastJson
  • 線上APIDocs:SpringFox
  • 模板引擎:freemarker
  • 等等···

當然僅僅擁有這些,還不足以完成一個專案的搭建,但是這些是我們構建的基石。使我們更加快速的開發。

怎樣來組裝一個基礎的專案,我們在上一期已經講完了,本期我們接著要完成一個基礎專案框架,同時呢還應該有一個基礎專案構建的思考。

1.怎麼完成安全校驗的登入

其實這個在上一季的專案中已經探討完成了,這一季只是說老生重談。甚至來講,登入是一個簡單的過程,卻不是個容易的東西。

首先我們應該做到:可靠、安全、有效。詳細說一下就是:傳輸過程加密,資料儲存加密,資訊伺服器存放,前端單純的展示。那麼我們常規的處理手段有:

  • 登入密碼傳輸前加密
  • 密文強效驗
  • 使用者資訊快取到session

具體的程式碼如下:

@Controller
@Api(description = "外層資訊,無需Shiro接管,整合檔案下載控制器")
public class MainController{

    @PostMapping(value = "/login", produces = MediaType.TEXT_HTML_VALUE)
    @ApiOperation(value = "/login", notes = "登入後臺系統")
    public String login(@ApiParam(hidden = true) ModelMap map,
                        @ApiParam(hidden = true) ShiroHttpServletRequest request,
                        @ApiParam(value = "使用者名稱不能為空,否則不允許登入"
                                , required = true) @RequestParam(value = "userLogin", required = false) String userLogin,
                        @ApiParam(value = "使用者密碼不能為空且必須為16位小寫MD5,否則不允許登入"
                                , required = true) @RequestParam(value = "userPass", required = false) String userPass) {
        User result = null;
        try {
            //1.得到Subject
            Subject subject = SecurityUtils.getSubject();
            //2.呼叫登入方法
            UsernamePasswordToken token = new UsernamePasswordToken(userLogin, userPass);
            subject.login(token);//當這一程式碼執行時,就會自動跳入到AuthRealm中認證方法
            result = (User) subject.getPrincipal();
            subject.getSession().setAttribute("userInfo", result);
            return "redirect:/endSys/index";
        } catch (Exception e) {
            e.printStackTrace();
            LogE.getInstance(this.getClass()).logOutLittle(e.getMessage());
            map.addAttribute("msg", e.getMessage());
            return "login";
        }

    }

    @GetMapping(path = "logOut", produces = MediaType.TEXT_HTML_VALUE)
    @ApiOperation(value = "退出登入", notes = "退出登入,清空session")
    public String logOut() {
        Subject subject = SecurityUtils.getSubject();
        if (subject.isAuthenticated()) {
            subject.getSession().removeAttribute("userInfo");
            subject.logout(); // session 會銷燬,在SessionListener監聽session銷燬,清理許可權快取
        }
        return "redirect:/";
    }
}

public class ShiroRealm extends AuthorizingRealm {

    @Autowired
    private UserServiceImpl userService;

    /*
     * 登入資訊和使用者驗證資訊驗證(non-Javadoc)
     * @see org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken user = (UsernamePasswordToken) token;
        LogE.getInstance(ShiroRealm.class).logOutLittle("開始登入====>\n使用者為:" + user.getUsername());

        String userLogin = user.getUsername();
        char[] password = user.getPassword();

        User loginResult = null;
        try {
            loginResult = userService.login(userLogin, new String(password));
        } catch (Exception e) {
            e.printStackTrace();
            LogE.getInstance(ShiroRealm.class).logOutLittle("登入異常結束====>\n使用者為:" + user.getUsername());
            throw new AuthenticationException(e.getMessage());
        }
        LogE.getInstance(ShiroRealm.class).logOutLittle("登入成功====>\n使用者為:" + user.getUsername());
        return new SimpleAuthenticationInfo(loginResult, user.getPassword(), this.getName());
    }
}

public class MyCredentialsMatcher extends SimpleCredentialsMatcher {

    /**
     * 密碼比較方法,有自己的登入校驗方法,故此繞過校驗
     *
     * @param token
     * @param info
     * @return
     */
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {

        return true;
    }
}複製程式碼

當然,其他的程式碼,都不是那麼核心,我們主要是圍繞著Shiro來闡述我們的登入。

為什麼我們的登入和登出地址不要Shiro接管呢?因為無論在登入或登出的時候有沒有使用者,我們都會執行對應的操作來分別存放使用者資訊或者清除使用者資訊。

但是僅僅有這個就能完成登陸校驗?錯!錯!錯! 我們需要把shiro接管的頁面都納入管理範圍內。也就會產生spring相關的設定,這些,在我們上一季都是有講到過。但是上一季是XML配置,這一次我們是java配置。


@Configuration
public class ShiroConfig {

    @Bean
    public ShiroRealm realm() {
        ShiroRealm myShiroRealm = new ShiroRealm();
        MyCredentialsMatcher matcher = new MyCredentialsMatcher();
        myShiroRealm.setCredentialsMatcher(matcher); //設定解密規則
        return myShiroRealm;
    }


    //SecurityManager 是 Shiro 架構的核心,通過它來連結Realm和使用者(文件中稱之為Subject.)
    @Bean
    public DefaultSecurityManager securityManager() {
        DefaultSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm()); //將Realm注入到SecurityManager中。

        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setGlobalSessionTimeout(1800000);   //預設三十分鐘

//        Cookie cookie = new SimpleCookie();     //設定cookie
//        cookie.setName("sid");  //java預設值是JSESSIONID
//        cookie.setDomain("acheng1314.cn");  //cookie作用域
//        cookie.setMaxAge(1800); //cookie超時時間30分鐘
//        cookie.setHttpOnly(true);
//
//        sessionManager.setSessionIdCookie(cookie);
//        sessionManager.setSessionIdCookieEnabled(true);

        //session會話驗證
//        ExecutorServiceSessionValidationScheduler sessionValidationScheduler = new ExecutorServiceSessionValidationScheduler();
//        sessionValidationScheduler.setInterval(3600000);
//        sessionValidationScheduler.setSessionManager(sessionManager);
//
//        sessionManager.setSessionValidationScheduler(sessionValidationScheduler);
//        sessionManager.setSessionValidationSchedulerEnabled(true);

        securityManager.setSessionManager(sessionManager);    //此處已經自動持有DefaultWebSessionManager

        return securityManager;
    }

    //在這裡配置url訪問規則
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultSecurityManager securityManager) {

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/logout", "logout");
        filterChainDefinitionMap.put("/favicon.ico", "anon");
        filterChainDefinitionMap.put("/static/*/**", "anon");

        //authc表示需要驗證身份才能訪問,還有一些比如anon表示不需要驗證身份就能訪問等。
        filterChainDefinitionMap.put("/druid/*/**", "authc");
        filterChainDefinitionMap.put("/endSys/*/**", "authc");
        filterChainDefinitionMap.put("/swagger-ui.html/*/**", "authc");


        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setSuccessUrl("/endSys/index");
//        shiroFilterFactoryBean.setUnauthorizedUrl("/403"); //這裡設定403並不會起作用,參考http://www.jianshu.com/p/e03f5b54838c

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

}複製程式碼

我們首先告訴spring框架這個是我們框架的設定,需要載入。 然後接著在這個設定裡面配置對應的bean(ShiroRealm、DefaultSecurityManager、ShiroFilterFactoryBean)來實現相應的排程規則。

一些具體的細節,如:前端登入頁面、資料庫操作等等,請查閱github倉庫程式碼或者訪問碼雲

到目前這裡,我們可以實現登入到系統首頁了:http://localhost:8181/login

登入成功後,簡單的主頁如下:

登入成功後的
登入成功後的

悄悄的告訴你,我後端主頁使用了zDrag來實現網頁內部窗體管理。

當然到了這裡還是有點小問題,那就是我們使用者資訊過期後,我們點選選單會產生內部窗體登入(登入成功後再點選選單會回到正確介面)我們新增一個js方法就能解決這個小問題。

總結

這一期主要討論了手腳腳需要的東西。

  • 專案基礎依賴
  • 較為安全的登入

下期預告

下期目標是產生程式碼生成器和選單樹。


如果你認可我所做的事情,並且認為我做的事對你有一定的幫助,希望你也能打賞我一杯咖啡,謝謝。

支付寶捐贈
支付寶捐贈

相關文章