1. 程式人生 > >spring boot 整合shiro(使用者授權和許可權控制)

spring boot 整合shiro(使用者授權和許可權控制)

(1) pom.xml中新增Shiro依賴

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.0</version>
</dependency>

(2) 注入Shiro Factory和SecurityManager

Shiro幾個核心的類,第一就是ShiroFilterFactory,第二就是SecurityManager,那麼最簡單的配置就是注入這兩個類就ok了

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.util.LinkedHashMap;
import java.util.Map;

@Component
@Configuration
public class ShiroConfig {
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        System.out.println("ShiroConfiguration.shirFilter()");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //攔截器.
        Map <String, String> filterChainDefinitionMap = new LinkedHashMap <String, String>();
//anon:所有url都都可以匿名訪問;
//authc: 需要認證才能進行訪問;
//user:配置記住我或認證通過可以訪問;
        // 配置不會被攔截的連結 順序判斷
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/authenRecharge/**", "anon");
        // 配置不會被攔截的連結 順序判斷
        filterChainDefinitionMap.put("/interface/**", "anon");
        filterChainDefinitionMap.put("/manage/**", "anon");
        filterChainDefinitionMap.put("/wicket/**", "anon");
        filterChainDefinitionMap.put("/dbwizard/**", "anon");
        filterChainDefinitionMap.put("/mallbook/login", "anon");
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/images/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/wicket/resource/**", "anon");
        //配置退出 過濾器,其中的具體的退出程式碼Shiro已經替我們實現了
        filterChainDefinitionMap.put("/logout", "logout");
        //<!-- 過濾鏈定義,從上向下順序執行,一般將/**放在最為下邊 -->:這是一個坑呢,一不小心程式碼就不好使了;
        //<!-- authc:所有url都必須認證通過才可以訪問; anon:所有url都都可以匿名訪問-->
        filterChainDefinitionMap.put("/**", "authc");
        // 如果不設定預設會自動尋找Web工程根目錄下的"/login.jsp"頁面
        shiroFilterFactoryBean.setLoginUrl("/");
        // 登入成功後要跳轉的連結
        shiroFilterFactoryBean.setSuccessUrl("/mallbook/MallHouse");

        //未授權介面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }
//這個很重要,將我們自定義的Realm注入到SecurityManager中
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }
    /**
     * 憑證匹配器
     *應為我們身份認證用的密碼是加密的,所以需要一個加密演算法 ,要是使用明文就不用了
     * (由於我們的密碼校驗交給Shiro的SimpleAuthenticationInfo進行處理了
     * 所以我們需要修改下doGetAuthenticationInfo中的程式碼; )
     *
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");//雜湊演算法:這裡使用MD5演算法;
        hashedCredentialsMatcher.setHashIterations(1);//雜湊的次數,比如雜湊兩次,相當於 md5(md5(""));
        return hashedCredentialsMatcher;
    }
//身份認證realm;
    @Bean
    public MyShiroRealm myShiroRealm() {
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myShiroRealm;
    }
    /**
     * 開啟shiro aop註解支援.
     * 使用代理方式;所以需要開啟程式碼支援;
     *
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new         
             AuthorizationAttributeSourceAdvisor();
            advisor.setSecurityManager(securityManager);
        return advisor;
    }

}

 

(3) 身份認證,許可權控制

認證實現

Shiro的認證過程最終會交由Realm執行,這時會呼叫Realm的getAuthenticationInfo(token)方法。

該方法主要執行以下操作:

1、檢查提交的進行認證的令牌資訊

2、根據令牌資訊從資料來源(通常為資料庫)中獲取使用者資訊

3、對使用者資訊進行匹配驗證。

4、驗證通過將返回一個封裝了使用者資訊的AuthenticationInfo例項。

5、驗證失敗則丟擲AuthenticationException異常資訊。

而在我們的應用程式中要做的就是自定義一個Realm類,繼承AuthorizingRealm抽象類,過載doGetAuthenticationInfo (),重寫獲取使用者資訊的方法。

shiro的認證最終是交給了Realm進行執行了,所以我們需要自己重新實現一個Realm,此Realm繼承AuthorizingRealm。

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

public class MallBookShiroRealm extends AuthorizingRealm {
    @Autowired
    private UpmsUserService userService;

    private final static Logger logger = LoggerFactory.getLogger(MallBookShiroRealm.class);

    /**
     * 許可權認證,為當前登入的Subject授予角色和許可權
     * <p>
     * 本例中該方法的呼叫時機為需授權資源被訪問時
     * 並且每次訪問需授權資源時都會執行該方法中的邏輯,這表明本例中預設並未啟用AuthorizationCache
     * 如果連續訪問同一個URL(比如重新整理),該方法不會被重複呼叫,Shiro有一個時間間隔(也就是cache時間,在ehcache-shiro.xml中配置),超過這個時間間隔再重新整理頁面,該方法會被執行
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        logger.info("許可權配置-->EpayShiroRealm.doGetAuthorizationInfo()");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        UserInfo userInfo = (UserInfo) principals.getPrimaryPrincipal();
        for (RolesInfo role : userInfo.getRoles()) {
            authorizationInfo.addRole(role.getName());
            for (UpmsPermission p : role.getPermissions()) {
                if (null != p.getPermissionValue() && p.getPermissionValue().length() > 0) {
                    authorizationInfo.addStringPermission(p.getPermissionValue());
                }
            }
        }
        return authorizationInfo;
    }


    /**
     * 主要是用來進行身份認證的,也就是說驗證使用者輸入的賬號和密碼是否正確
     *
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {

        logger.info("正在驗證身份...");
        // 獲取使用者的輸入的賬號.
        //將token轉換成UsernamePasswordToken
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String username = (String) token.getPrincipal();
        System.out.println(token.getCredentials());

        // 通過username從資料庫中查詢 User物件,如果找到,沒找到.
        // 實際專案中,這裡可以根據實際情況做快取,如果不做,Shiro自己也是有時間間隔機制,2分鐘內不會重複執行該方法
        UserInfo userInfo = userService.findByUsername(username);
        System.out.println("----->>userInfo=" + userInfo);
        if (null == userInfo) {
            return null;
        }
        // 得到鹽值加密後的密碼:只用於方便資料庫測試,後期不會用到。
        return new SimpleAuthenticationInfo(
                userInfo,
                userInfo.getPassword(),
                ByteSource.Util.bytes(userInfo.getSalt()),
                getName()
        );
    }
}

繼承AuthorizingRealm主要需要實現兩個方法:
doGetAuthenticationInfo();
doGetAuthorizationInfo();
其中doGetAuthenticationInfo主要是用來進行身份認證的,也就是說驗證使用者輸入的賬號和密碼是否正確。
SimpleAuthenticationInfo oauthenticationInfo =
  return new SimpleAuthenticationInfo(
                userInfo,//使用者名稱
                userInfo.getPassword(),//密碼
                ByteSource.Util.bytes(userInfo.getSalt()),//加密的鹽
                getName()//realm name
        );


doGetAuthorizationInfo()是許可權控制,當訪問到頁面的時候,使用了相應的註解或者shiro標籤才會執行此方法否則不會執行,所以如果只是簡單的身份認證沒有許可權的控制的話,那麼這個方法可以不進行實現,直接返回null即可。
主要的類:SimpleAuthorizationInfo 
  for (RolesInfo role : userInfo.getRoles()) {
            authorizationInfo.addRole(role.getName());//新增角色
            for (UpmsPermission p : role.getPermissions()) {
                if (null != p.getPermissionValue() && p.getPermissionValue().length() > 0) {
                    authorizationInfo.addStringPermission(p.getPermissionValue());//新增選單
                }
            }
        }

到這裡的話身份認證許可權控制基本是完成了,最後我們在編寫一個登入的時候,登入的處理

import java.util.ArrayList;
import java.util.List;
@RestController
public class LoginController{
    Logger log= LoggerFactory.getLogger(LoginController.class);
    @Autowired
    private UpmsUserService userService;

    @RequestMapping(value = "/ajaxLogin", method = RequestMethod.POST)
    public Object login(String username, String password) {
        String passwordNew= RSAUtils.decrypt(password, RSAUtils.getPrivateKey(Constant.PWD_PRIVATE_KEY));
        UsernamePasswordToken token = new UsernamePasswordToken(username, passwordNew);
        token.setHost("localhost");
        Subject currentUser = SecurityUtils.getSubject();

        UpmsResultConstant messageCode = UpmsResultConstant.LOGIN_SUCCESS;
        if (null == userService.getUserInfo(token.getUsername())) {
            return new UpmsResult(UpmsResultConstant.FAILED, UpmsResultConstant.FAILED.code);
        }
        try {
            currentUser.login(token);
        } catch (UnknownAccountException ex) {
            messageCode = UpmsResultConstant.LOGIN_ACCOUNT_ERROR;
            ex.printStackTrace();
        } catch (IncorrectCredentialsException ex) {
            messageCode = UpmsResultConstant.LOGIN_PASSWORD_ERROR;
            ex.printStackTrace();
        } catch (LockedAccountException ex) {
            messageCode = UpmsResultConstant.LOGIN_ACCOUNT_LOCKED;
            ex.printStackTrace();
        } catch (ExcessiveAttemptsException ex) {
            messageCode = UpmsResultConstant.LOGIN_EXCESSIVE_ERROR;
            ex.printStackTrace();
        } catch (AuthenticationException ex) {
            messageCode = UpmsResultConstant.LOGIN_AUTHTICATION_FAIL;
            ex.printStackTrace();
        }

        if (currentUser.isAuthenticated()) {
            LoginInfo loginInfo = new LoginInfo();
            UserInfo userInfo = (UserInfo) currentUser.getPrincipals().getPrimaryPrincipal();
            loginInfo.setAvatar(userInfo.getAvatar());
            loginInfo.setIntroduction(userInfo.getRole());
            loginInfo.setName(userInfo.getUserName());
            loginInfo.setToken(currentUser.getSession().getId().toString());
            if (null != userInfo.getRoles() && userInfo.getRoles().size() > 0) {
                List<String> roles = new ArrayList<>();
                for (RolesInfo rolesInfo : userInfo.getRoles()) {
                    roles.add(rolesInfo.getName());
                }
                loginInfo.setRole(roles);
            } else {
                return new UpmsResult(UpmsResultConstant.FAILED, UpmsResultConstant.FAILED.code);
            }

            return new UpmsResult(UpmsResultConstant.SUCCESS, loginInfo);
        } else {
            return new UpmsResult(messageCode, 0);
        }
    }

 
    @RequestMapping(value = "/getUserInfo")
    public Object getUserInfo() {
        UserInfo userInfo = UserUtils.getUser();
        LoginInfo loginInfo = new LoginInfo();
        loginInfo.setAvatar(userInfo.getAvatar());
        loginInfo.setIntroduction(userInfo.getRole());
        loginInfo.setName(userInfo.getRealName());
        loginInfo.setToken(userInfo.getToken());
        List<String> roles = new ArrayList<String>();
        List<String> pms = new ArrayList<String>();
        for (RolesInfo rolesInfo : userInfo.getRoles()) {
 
            roles.add(rolesInfo.getTitle());
            for (UpmsPermission permission : rolesInfo.getPermissions()) {
                pms.add(permission.getPermissionValue());
            }
        }
        loginInfo.setPms(pms);
        loginInfo.setRole(roles);
        loginInfo.setMchId(userInfo.getUserId().toString());
        return new UpmsResult(UpmsResultConstant.SUCCESS, loginInfo);
    }
}

這個時候訪問http://localhost:9527/#/login就可以跳轉到index頁面了

 

此時身份認證是好了,但是許可權控制好像還沒有作用

還需要兩部分:

第一就是開啟shiro aop註解支援

在ShiroConfig需要開啟shiro aop註解支援.

@Bean

public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {

AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();

advisor.setSecurityManager(securityManager);

return advisor;

}

第二就是在controller方法中加入相應的註解:

@RequiresPermissions("sys_user_add")//許可權管理

@RestController
@RequestMapping(value = "/manage/user")
public class UserController {
    @Autowired
    private UpmsUserService userService;
    private static Logger log = LoggerFactory.getLogger(UserController.class);
    @Autowired
    IdWorker idWorker;

   

    @RequiresPermissions("user_manager")
    @RequestMapping(value = "/list", method = RequestMethod.POST)
    @ResponseBody
    public Object list(UpmsUser user) {
        return new UpmsResult(
                UpmsResultConstant.SUCCESS
                , userService.listUser(user)
        );
    }
    
    @RequiresPermissions("sys_user_edit")
    @RequestMapping(value = "/update", method = RequestMethod.POST)
    public Object update(UpmsUser upmsUser) {

        log.info("-------------------------------");
        // 邏輯驗證
        ComplexResult result = FluentValidator.checkAll()
                .doValidate()
                .result(ResultCollectors.toComplex());
        if (!result.isSuccess()) {
            return new UpmsResult(UpmsResultConstant.INVALID_LENGTH, result.getErrors());
        }
        String passwordNew = "";
        if (StringUtils.isNotEmpty(upmsUser.getPassword())) {
            passwordNew = RSAUtils.decrypt(upmsUser.getPassword(), RSAUtils.getPrivateKey(Constant.PWD_PRIVATE_KEY));
            // 不允許直接改密碼
            Object md = new SimpleHash("MD5", passwordNew, upmsUser.getSalt(), 1);
            upmsUser.setPassword(md.toString());
            upmsUser.setVerifyPassword(DictConstant.USER_HAS_LOGGED);
        }
//        upmsUser.setPassword(null);
        int count = userService.updateUser(upmsUser);
        return new UpmsResult(UpmsResultConstant.SUCCESS, count);
    }

    @RequiresPermissions("sys_user_edit")
    @RequestMapping(value = "/updatePassword", method = RequestMethod.POST)
    public Object updatePassword(UpmsUser upmsUser) {
        String passwordNew = RSAUtils.decrypt(upmsUser.getPassword(), RSAUtils.getPrivateKey(Constant.PWD_PRIVATE_KEY));
        // 不允許直接改密碼
        Object md = new SimpleHash("MD5", passwordNew, ByteSource.Util.bytes(upmsUser.getSalt()), 1);
        if (md.toString().equals(UserUtils.getUser().getPassword())) {
            return new UpmsResult(UpmsResultConstant.PASSWORD_REPEAT, 0);
        }
        upmsUser.setPassword(md.toString());
        upmsUser.setVerifyPassword(DictConstant.USER_HAS_LOGGED);
        int count = userService.updateUser(upmsUser);
        return new UpmsResult(UpmsResultConstant.SUCCESS, count);
    }

    @RequiresPermissions("sys_user_disabled")
    @RequestMapping(value = "/locked", method = RequestMethod.POST)
    public Object locked(UpmsUser upmsUser) {
        int count = userService.updateUser(upmsUser.getUserId(), StatusEnum.FORBIDDEN.getByteVal());
        return new UpmsResult(UpmsResultConstant.SUCCESS, count);
    }

    @RequiresPermissions("sys_user_enabled")
    @RequestMapping(value = "/unlock", method = RequestMethod.POST)
    public Object unlock(UpmsUser upmsUser) {
        int count = userService.updateUser(upmsUser.getUserId(), StatusEnum.NORMAL.getByteVal());
        return new UpmsResult(UpmsResultConstant.SUCCESS, count);
    }

    @RequestMapping(value = "listCurrentUser", method = RequestMethod.POST)
    public Object listCurrentUser() {
        return new UpmsResult(
                UpmsResultConstant.SUCCESS
                , userService.selectUserInfoById());
    }
}

到此,身份認證及許可權管理已完成,如果還要加入快取機制,還要引入依賴

參考:spring boot學習教程