東川印記

一本東川,笑看爭龍斗虎;寰茫兦者,度橫佰昧人生。

简单学习Shiro

2022年7月5日星期二



Apache Shiro 是一个Java安全框架,用于 身份验证、授权、密码和会话管理

核心组件:Subject、SecurityManager、Realmes。

Subject代表当前用户安全操作。

SecurityManager管理所有用户安全操作。Facade外观模式;

Realme负责登录和授权(访问控制)。

shiro内置了大量安全数据源的Realme,如LDAP、JDBC、类似INI的文本配置及属性文件等。


1,简单使用

1)环境搭建

建立SpringBoot项目,引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>

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

2)application.prop配置thymeleaf

# THYMELEAF (ThymeleafAutoConfiguration)
# 开启模板缓存(默认值: true )
# 开发阶段false不然看不到实时页面
spring.thymeleaf.cache=false
# 关闭h5语法验证
spring.thymeleaf.mode=LEGACYHTML5

3)新建h5页面

在resource/templates下

login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body>

<form action="login2" method="post">
    账号 <input type="text" name="username"> <br/>
    密码 <input type="text" name="password" id="password"> <br/>

    <input type="submit" value="登录" id="loginBtn">
</form>
<span th:text="${errorMessage}"/>

</body>
</html>

success.html

noPermission.html

固定页面


4)config

//spring配置类
@Configuration
public class ShiroConfig {

    @Bean
    public SecurityManager getSecurityManager(Realm realm) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(realm);
        return manager;
    }

    //自定义Realm,用来认证和授权
    @Bean
    public Realm getRealm() {
//        CustomRealm realm = new CustomRealm(); //测试1
        CustomAuthRealm realm = new CustomAuthRealm();//测试2
        return realm;
    }

    //过滤器bean,配置shiro相关规则拦截
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);

        bean.setLoginUrl("/"); //需要登录时跳转的地址
        bean.setSuccessUrl("/success"); //登陆成功跳转地址
        bean.setUnauthorizedUrl("/noPermission");//没权限跳转地址

        //权限拦截规则
        Map<String, String> filterMap = new LinkedHashMap<>();
        filterMap.put("/login", "anon"); //anon表示某个请求不需认证
        filterMap.put("/login2", "anon"); //anon表示某个请求不需认证
        filterMap.put("/logout", "logout"); //登出后跳转

        filterMap.put("/admin/**", "authc");//authc需要认证
        filterMap.put("/user/**", "authc");//authc需要认证

        filterMap.put("/**", "authc");//剩余的也都需要登录

        bean.setFilterChainDefinitionMap(filterMap);

        return bean;
    }

}

其中自定义的Realm配置

public class CustomRealm implements Realm {
    @Override
    public String getName() {
        return null;
    }

    @Override
    public boolean supports(AuthenticationToken authenticationToken) {
        return false;
    }

    @Override
    public AuthenticationInfo getAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        return null;
    }
}

包含了简单鉴权的Realm

public class CustomAuthRealm extends AuthenticatingRealm {

    /**
     * 用户认证
     * @param authenticationToken 用户身份,存有账号密码
     * @return 登陆成功后身份证明
     * @throws AuthenticationException 认证失败异常
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String name = token.getUsername();
        String pwd = new String(token.getPassword());
        System.out.println(name +"    "+pwd);

        if("11".equals(name)){
            throw new UnknownAccountException(); //未知账号
        }
        if("22".equals(name)){
            throw new LockedAccountException(); //账号锁定异常
        }

        //数据库中账号、密码、当前Realm名字
        return new SimpleAuthenticationInfo(name,"123456",getName()); //创建认证对象,认证成功
    }
}

5)最后,定义controller发布api

@Controller
public class TestController {

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

    @RequestMapping("/login2")
    public String login2(String username, String password, Model model) {
        Subject subject = SecurityUtils.getSubject();//权限操作对象

        //先登出,避免缓存问题
        subject.logout();

        //没有登陆过
        if (!subject.isAuthenticated()) {
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password); //身份令牌

            try {
                subject.login(usernamePasswordToken);
            } catch (UnknownAccountException e) {
                model.addAttribute("errorMessage", "账号错误");
                return "login";
            } catch (LockedAccountException e) {
                model.addAttribute("errorMessage", "账号被锁定");
                return "login";
            } catch (IncorrectCredentialsException e) {
                model.addAttribute("errorMessage", "密码错误");
                return "login";
            } catch (AuthenticationException e) {
                model.addAttribute("errorMessage", "认证失败");
                e.printStackTrace();
                return "login";
            }

        }

        return "redirect:/success";
    }

    @RequestMapping("/login")
    public String login() {
        return "redirect:/success";
    }

    @RequestMapping("/logout")
    public String logout() {
        SecurityUtils.getSubject().logout();
        return "redirect:/";
    }

    @RequestMapping("/success")
    public String success() {
        return "success";
    }

    @RequestMapping("/noPermission")
    public String noPermission() {
        return "noPermission";
    }

    @RequestMapping("/admin/test")
    public @ResponseBody
    String adminTest() {
        return "/admin/test 请求";
    }

    @RequestMapping("/user/test")
    public @ResponseBody
    String userTest() {
        return "/user/test 请求";
    }

}


6)测试

请求 http://localhost:8080

2,密码加密

1)测试Realm加密设置

/**
     * 用户认证
     * @param authenticationToken 用户身份,存有账号密码
     * @return 登陆成功后身份证明
     * @throws AuthenticationException 认证失败异常
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String name = token.getUsername();
        String pwd = new String(token.getPassword());
        System.out.println(name +"    "+pwd);

        if("11".equals(name)){
            throw new UnknownAccountException(); //未知账号
        }
        if("22".equals(name)){
            throw new LockedAccountException(); //账号锁定异常
        }


        //加密应该在客户端上加密后传输到服务器,不应该在服务器端加密
        //数据来源密码 加密
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("MD5");
        matcher.setHashIterations(2);
        setCredentialsMatcher(matcher);  //设置加密规则


        //数据库中存储的密码 加密 模拟
        SimpleHash md5 = new SimpleHash("MD5", "123456", "", 2);

        //数据库中账号、密码、当前Realm名字
//        return new SimpleAuthenticationInfo(name,"123456",getName()); //创建认证对象,认证成功
        return new SimpleAuthenticationInfo(name,md5,getName()); //创建认证对象,认证成功
    }

2)浏览器加密传输到服务器

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>login</title>
    <script th:src="@{|/js/jquery-1.11.3.min.js|}"></script>
    <script th:src="@{|/js/jQuery.md5.js|}"></script>
    <script>
        $(function () {
            $("#loginBtn").bind("click", function () {
                alert(1);
                var md5Str = $.md5($("#password").val());
                alert(md5Str);
                $("#md5Password").val(md5Str);
            })
        });

        // $(document).ready(function(){
        //     $("loginBtn").click(function(){
        //         alert(2);
        //     });
        // });
    </script>
</head>
<body>

<form action="login2" method="post">
    账号 <input type="text" name="username"> <br/>
    密码 <input type="text" id="password"> <br/>

    <input type="submit" value="登录" id="loginBtn">

    <input type="hidden" name="password" id="md5Password"/>
</form>
<span th:text="${errorMessage}"/>


</body>
</html>


3,角色分配

再重新定义一个Realm,并使用

/**
 * 既能认证,也能授权
 *
 * @author senrsl
 * @ClassName: CustomAuth2Realm
 * @Package: dc.test.shiro.realm
 * @CreateTime: 2022/7/5 17:26
 */
public class CustomAuth2Realm extends AuthorizingRealm {


    /**
     * 用户认证
     *
     * @param authenticationToken 用户身份,存有账号密码
     * @return 登陆成功后身份证明
     * @throws AuthenticationException 认证失败异常
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String name = token.getUsername();
        String pwd = new String(token.getPassword());
        System.out.println(name + "    " + pwd);

        if ("11".equals(name)) {
            throw new UnknownAccountException(); //未知账号
        }
        if ("22".equals(name)) {
            throw new LockedAccountException(); //账号锁定异常
        }


//        //加密应该在客户端上加密后传输到服务器,不应该在服务器端加密
//        //数据来源密码 加密
//        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//        matcher.setHashAlgorithmName("MD5");
//        matcher.setHashIterations(2);
//        setCredentialsMatcher(matcher);  //设置加密规则
//
//
//        //数据库中存储的密码 加密 模拟
//        SimpleHash md5 = new SimpleHash("MD5", "123456", "", 2);

        //数据库中账号、密码、当前Realm名字
//        return new SimpleAuthenticationInfo(name,"123456",getName()); //创建认证对象,认证成功
//        return new SimpleAuthenticationInfo(name,md5,getName()); //创建认证对象,认证成功
        return new SimpleAuthenticationInfo(name, "e10adc3949ba59abbe56e057f20f883e", getName()); //创建认证对象,认证成功
    }


    /**
     * 用户授权
     * 用户认证通过后 授权
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("用户授权方法");
        Object primaryPrincipal = principalCollection.getPrimaryPrincipal();//获取用户账号,根据账号从数据库读取角色

        //每次点击都去请求数据库,效率问题
        Set<String> setRoles = new HashSet<>(); //模拟角色配置
        //admin账号有admin权限
        //http://localhost:8080/admin/test
        if ("admin".equals(primaryPrincipal)) {
            setRoles.add("admin");
            setRoles.add("users");
        }

        //user账号只有user权限
        //http://localhost:8080/user/test
        if ("user".equals(primaryPrincipal)) {
            setRoles.add("users");
        }

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setRoles(setRoles); //添加角色关系

        return info;
    }
}

请求地址增加角色过滤

//        filterMap.put("/admin/**", "authc");//authc需要认证
        filterMap.put("/admin/**", "authc,roles[admin]");//授权admin权限

使用success.html测试

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
登陆成功
<br/>
<br/>
<a href="/logout">登出</a>
<br/>

<a th:href="@{|/admin/test|}">需要有admin权限</a> <br/>
<a th:href="@{|/user/test|}">需要有user权限</a> <br/>

</body>
</html>

用户登陆后,根据角色权限,判断跳转admin或user是否有权限。

4,基于注解的角色配置

注释掉之前的权限配置

        //改用注解
//        filterMap.put("/admin/**", "authc,roles[admin]");//授权admin权限
//        filterMap.put("/user/**", "authc");//authc需要认证

开启注解支持

ShroConfig添加

/**
 * 开启shiro注解支持
 * @RequireRoles()@RequirPermissions()
 * @return
 */
@Bean
public DefaultAdvisorAutoProxyCreator creator(){
    DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
    creator.setProxyTargetClass(true);

    return creator;
}

/**
 * 开启AOP支持
 * @param securityManager
 * @return
 */
@Bean
public AuthorizationAttributeSourceAdvisor advisor(SecurityManager securityManager){
    AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
    advisor.setSecurityManager(securityManager);
    return advisor;

}

controller添加注解

@RequiresRoles(value = {"admin"}) //必须要有什么样的角色 value一个或多个角色名,logical ANDOR
@RequestMapping("/admin/test")
public @ResponseBody String adminTest() {
    return "/admin/test 请求";
}

@RequiresRoles(value = {"user"})
@RequestMapping("/user/test")
public @ResponseBody String userTest() {
    return "/user/test 请求";
}


//自定义异常拦截
@ExceptionHandler(value = {AuthorizationException.class})
public String permissionError(Throwable throwable) {
    return "noPermission"; //noPermission.html
}


5,权限配置

realm中除增加角色配置外,还可以增加权限配置

//权限配置
Set<String> setPermission = new HashSet<>();
if("admin".equals(primaryPrincipal)){
    setPermission.add("admin:add");//只是命名风格,表示 adminadd功能
}

SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setRoles(setRoles); //添加角色关系
info.setStringPermissions(setPermission); //添加权限

controller中使用

    @RequiresRoles(value = {"admin"}) //必须要有什么样的角色 value一个或多个角色名,logical ANDOR
    @RequiresPermissions(value = {"admin:add"}) //当前用户是否有权限
    @RequestMapping("/admin/add")
    public @ResponseBody String adminAdd() {
//        //除了给予配置的权限验证、注解的权限验证,还支持基于方法的权限验证
//        Subject subject = SecurityUtils.getSubject();
//        subject.checkRole();//验证角色
//        subject.checkPermission();//验证权限
        return "/admin/add 请求";
    }


6,集成Shiro标签到Thymleaf

依赖

<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>

shiroConfig添加bean

/**
 * themeleaf 扩展 shiro
 * @return
 */
@Bean
public ShiroDialect shiroDialect(){
    return new ShiroDialect();
}

添加命名空间

基于属性使用

基于标签使用

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
登陆成功
<br/>
<br/>
<a href="/logout">登出</a>
<br/>

<a th:href="@{|/admin/test|}" shiro:hasRole="admin">需要有admin权限</a> <br/>
<a th:href="@{|/admin/add|}">需要有admin:add权限</a> <br/>
<shiro:hasRole name="user">
<a th:href="@{|/user/test|}">需要有user权限</a> <br/>
</shiro:hasRole>

</body>
</html>


--
senRsl
2022年07月05日10:15:49

没有评论 :

发表评论