springboot整合shiro入門

   shiro作爲安全框架,使用還是比較多,而且相對來說,比較容易上手。下面將使用springboot來整合shiro,實現的功能如下:

(1)實現訪問控制,未登錄時,只能訪問登錄接口

(2)實現角色和權限的訪問控制

示例代碼,已放在了GitHub上:https://github.com/qiuxinfa/shiro-study

先看下目錄結構:

 

1.maven依賴:

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

 

2.創建一個用戶類User:

public class User {
    private String username;
    private String password;
    private Integer enable;  //賬號是否鎖定
    //省略set、get、toString方法
}

 

3.創建一個業務查詢類,模擬數據存儲和數據查詢功能,因爲這裏沒有連接數據庫:

@Service
public class UserService {

    //用來存儲用戶信息的map
    private Map<String,User> userInfo = new HashMap<>(3);

    public UserService(){
        //用戶信息
        userInfo.put("admin",new User("admin","123",1));
        userInfo.put("sam",new User("sam","123",1));
        userInfo.put("qxf",new User("qxf","123",0));
    }

    //根據用戶名,查找用戶,模擬數據庫查詢
    public User getUserByUsername(String username){
        return userInfo.get(username);
    }

    //獲取所有的用戶
    public Map<String,User> getAllUser(){
        return userInfo;
    }

}

這裏爲了簡單起見,用一個map來存儲用戶信息,實際使用中,用戶信息肯定是存儲在數據庫中的。

 

4.自定義身份認證和授權的realm,繼承了AuthorizingRealm ,重寫認證和授權方法:

public class MyRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    //授權
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        User user = (User)SecurityUtils.getSubject().getPrincipal();  //獲取當前登錄的用戶信息

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //這裏模擬數據庫,進行角色和權限的處理
        //這裏只是簡單模擬,角色使用是用戶名,權限是包含用戶名的路徑
        simpleAuthorizationInfo.addRole(user.getUsername());
        simpleAuthorizationInfo.addStringPermission("/"+user.getUsername());

        return simpleAuthorizationInfo;
    }

    //認證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String username = (String) token.getPrincipal();
        //這裏模擬數據庫查詢用戶,根據用戶名查詢
        User dbUser = userService.getUserByUsername(username);
        if (dbUser == null){
            //賬號不存在
            throw new UnknownAccountException();
        }
        if (dbUser.getEnable()==0){
            //賬號被鎖定
            throw new LockedAccountException();
        }
        return new SimpleAuthenticationInfo(dbUser, dbUser.getPassword(), getName());
    }
}

 

5.配置shiro

/**
 *shiro配置
 */
@Configuration
public class ShiroConfig {
    @Bean
    MyRealm myRealm() {
        //返回自定義的身份認證和授權realm
        return new MyRealm();
    }

    @Bean
    SecurityManager securityManager() {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(myRealm());
        return manager;
    }

    @Bean
    ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager());
        //設置登錄的url
        bean.setLoginUrl("/login");
        Map<String, String> map = new LinkedHashMap<>();
        //anon表示,匿名訪問,不需要認證就可以訪問的接口
        map.put("/doLogin", "anon");
        //authc表示需要身份認證後纔可以訪問
        map.put("/**", "authc");
        bean.setFilterChainDefinitionMap(map);
        return bean;
    }

 //下面配置的3個bean,主要是爲了Shiro的註解(如@RequiresRoles,@RequiresPermissions)生效
    @Bean
    public  LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

  
    @Bean
    public  DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

這裏需要注意一下,如果想要使用shiro的註解生效,需要這3個bean:

(1)LifecycleBeanPostProcessor 

(2)DefaultAdvisorAutoProxyCreator

(3)AuthorizationAttributeSourceAdvisor

 

6.定義訪問接口:

@Controller
public class ShiroController {

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

    @PostMapping("/doLogin")
    @ResponseBody
    public String doLogin(String username,String password){
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            subject.login(token);
            return "登錄成功";
        }catch (UnknownAccountException|IncorrectCredentialsException e){
            e.printStackTrace();
            return "賬號或密碼錯誤";
        }catch (LockedAccountException e){
            e.printStackTrace();
            return "賬號已被鎖定,請聯繫管理員";
        }catch (AuthenticationException e){
            e.printStackTrace();
            return "未知異常,請聯繫管理員";
        }

    }

    @GetMapping("/hello")
    @ResponseBody
    public String hello(){
        return "hello:"+SecurityUtils.getSubject().getPrincipal();
    }


}

這裏定義了3個接口:

(1)login,處理get請求,匿名可訪問,會跳轉到login.html登錄頁面

(2)doLogin,處理post請求,實現真正的登錄功能

(3)hello,獲取自身信息,認證後纔可以訪問

 

7.登錄頁面,寫得很簡陋,就是一個簡單HTML的form表單提交:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

   <form action="/doLogin" method="post">
       用戶名:<input type="text" name="username" />
       <br/>
       密  碼:<input type="password" name="password"/>
       <br/>
       <input type="submit" value="登錄"/>
   </form>

</body>
</html>

 

8.測試

(1)驗證這個功能:未登錄時,只能訪問登錄接口

寫個啓動類,啓動項目後,在瀏覽器輸入http://localhost:8080/hello,則會跳轉到登錄頁面,因爲在UserService裏面添加了3個用戶:

        //用戶信息
        userInfo.put("admin",new User("admin","123",1));
        userInfo.put("sam",new User("sam","123",1));
        userInfo.put("qxf",new User("qxf","123",0));

所以,當我們使用admin或者sam登錄時(密碼都是123),就可以登錄成功(qxf賬號是被鎖定了),登錄成功之後,再訪問hello就可以看到自己的信息了。

 

(2)驗證角色和權限訪問控制:

    在MyRealm的授權方法中,我是用 用戶名作爲角色添加了,用 /+用戶名作爲權限,真實項目肯定不是這樣的,一般會配置在數據庫中,這裏只是爲了簡單起見:

        //這裏模擬數據庫,進行角色和權限的處理
        //這裏只是簡單模擬,角色使用是用戶名,權限是包含用戶名的路徑
        simpleAuthorizationInfo.addRole(user.getUsername());
        simpleAuthorizationInfo.addStringPermission("/"+user.getUsername());

添加只有admin可以訪問的接口:

@RestController
@RequestMapping("/admin")
public class AdminController {

    @Autowired
    private UserService userService;

    @GetMapping("/allUser")
    @RequiresRoles("admin")    //表示需要有[admin]這個角色纔可以訪問
    @RequiresPermissions("/admin") //表示需要有 /admin 權限纔可以訪問
    public Collection<User>  allUser(){
        List<User> list = new ArrayList<>();
        Map<String,User> userMap = userService.getAllUser();
        return userMap.values();
    }
}

再寫一個全局異常處理,用以捕獲沒有權限訪問的異常:

/**
 * @Auther: qiuxinfa
 * @Date: 2020/4/2
 * @Description: 定義全局異常處理
 */
@RestControllerAdvice
public class MyGlobalException {

    //表示只捕獲AuthorizationException類及其子類異常
    @ExceptionHandler(AuthorizationException.class)
    public String handlerAuthorizationException(AuthorizationException e){
        e.printStackTrace();
        return "你沒有權限訪問";
    }
}

現在,可以啓動項目了,如果用sam登錄後,訪問http://localhost:8080/admin/allUser,那麼就會返回如下信息:

你沒有權限訪問

後臺也會打印出,需要角色admin纔可以訪問,其實還需要/admin權限纔可以訪問

如果用admin登錄後,訪問http://localhost:8080/admin/allUser,則會返回所有的用戶信息:

[{"username":"admin","password":"123","enable":1},{"username":"sam","password":"123","enable":1},{"username":"qxf","password":"123","enable":0}]

這樣就實現了角色的訪問控制,權限也是一樣的道理,爲了簡單起見,demo中儘量返回的是json,只有一個登錄頁面。

其他注意點:

        授權不是認證之後就立馬執行的,而是當你訪問某個資源需要權限時,纔會執行授權的方法,比如訪問添加了

@RequiresRoles,RequiresPermissions註解的時候,纔會觸發進入到我們自定義realm的授權方法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章