springboot - shiro - 简单验证与授权

25 篇文章 0 订阅
14 篇文章 0 订阅

最近开发个人博客,主要内容部分已经完成,就到了设计权限的部分,于是把shiro翻出来复习了一下,现在记录一下测试过程:

0. shiro逻辑

参考https://www.jianshu.com/p/7464327c83fe

//创建一个默认SecurityManager
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//创建一个自定义Realm对象
CustomRealm realm = new CustomRealm();
//将自定义Realm注入到SecurityManager里
defaultSecurityManager.setRealm(realm);
//创建加密Matcher,加密方式为md5,加密次数1
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("md5");
matcher.setHashIterations(1);
//注入到Realm中,这样在验证的时候,会把token传进去的密码自动加密
realm.setCredentialsMatcher(matcher);
//获取Subject
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
//设置Token
AuthenticationToken token = new UsernamePasswordToken("Hiway","123456");
//登录验证
subject.login(token);
//是否成功验证
System.out.println("isAuthentication:"+subject.isAuthenticated());
//是否拥有角色
subject.checkRoles("admin");
//是否拥有权限
subject.checkPermission("user:delete");
//登出
subject.logou();

shiro的主要模块:
在这里插入图片描述

1. 导入依赖

        <!--导入shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.5.3</version>
        </dependency>

2. 配置ShiroConfig

@Configuration
public class ShiroConfig {
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(){

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

        //设置登录页面
        shiroFilterFactoryBean.setLoginUrl("/login");
        Map<String,String> filterChainDefinitionMap=new LinkedHashMap<>();
		//先测一个queryAll的授权
		//"anon"可以匿名登录,比如filterChainDefinitionMap.put("/index","anon");
		//"perms"授权 比如filterChainDefinitionMap.put("/api/queryAll","perms[user:form]");
		//表示必须有user:form权限
        filterChainDefinitionMap.put("/api/queryAll","authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

	//开启shiro的注解  这部份是thymeleaf用的
	// @Bean(name = "shiroDialect")
	//public ShiroDialect shiroDialect() {
	//  return new ShiroDialect();
	//}

	
	
    @Bean
    public SecurityManager getSecurityManager(){
        DefaultSecurityManager defaultSecurityManager=new DefaultWebSecurityManager();
        //将自定义的customRealm放入DefaultSecurityManager中
        defaultSecurityManager.setRealm(customRealm());
        return defaultSecurityManager;
    }


    @Bean
    public CustomRealm customRealm(){
        CustomRealm customRealm = new CustomRealm();
        //自定义加密类new MyCredentialsMatcher()
        customRealm.setCredentialsMatcher(new MyCredentialsMatcher());
        return customRealm;
    }


    /**
     * *
     * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * *
     * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
     * * @return
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {

        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();

        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {

        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(getSecurityManager());
        return authorizationAttributeSourceAdvisor;
    }

}

3. 自定义加密类MyCredentialsMatcher()

public class MyCredentialsMatcher extends SimpleCredentialsMatcher {
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        String userName = (String) token.getPrincipal();
        String userPwd = new String((char[]) token.getCredentials());
        //MD5加盐,并迭代两次
        String md5Pwd = new SimpleHash("MD5", userPwd,
                ByteSource.Util.bytes(userName), 2).toHex();

        String accountCredentials = (String) getCredentials(info);

        return super.equals(md5Pwd, accountCredentials);
    }
}

4. 自定义CustomRealm

public class CustomRealm extends AuthorizingRealm {


    //授权
    @Override
    public AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("=====授权认证======");
        //获取用户名
        String username = (String) SecurityUtils.getSubject().getPrincipal();

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //通过username从数据库中查询权限信息,这里为了测试就直接写死,不查了!
        Set<String> stringSet = new HashSet<>();

        //增加两个授权
        stringSet.add("user:show");
        stringSet.add("user:admin");

        info.setStringPermissions(stringSet);
        return info;

    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        System.out.println("=====身份认证======");
        //通过令牌拿到userName和userPwd
        String userName = (String) authenticationToken.getPrincipal();
        String userPwd = new String((char[]) authenticationToken.getCredentials());

        //根据用户名从数据库获取密码,加密方法见CustomRealm的CredentialsMatcher属性设置
        //查询数据库,判断该用户是否存在,这里为了测试也不查了,写死。就是用户名:xinxin,密码:123456
        //自定义加密后的值:4a64f6bf6c50a9fc822e0bec4248c818

        String password = "4a64f6bf6c50a9fc822e0bec4248c818";
        if (StringUtils.isEmpty(userName)) {
            throw new UnknownAccountException("用户名为空");
        } 
        
        return new SimpleAuthenticationInfo(userName, password, getName());
    }

5. Controller层

   //登录
    @PostMapping("/login")
    public Object toLogin(@RequestParam("username") String username, @RequestParam("password") String password) {
        //从SecurityUtils里边创建一个subject
        Subject subject = SecurityUtils.getSubject();
        //在认证提交前准备token(令牌)
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        //自定义返加的token,登录后自动分配一个线程Id
        String token_res = subject.getSession().getId().toString();
        Map<String,Object> map=new HashMap<>();
        map.put("token",token_res);


        //执行认证登陆,抛出的异常在GlobalExceptionHandler中处理
        subject.login(token);
        if (subject.isAuthenticated()) {
            return ApiResult.succ(map);
        } else {
            token.clear();
            return new ResponseEntity<>("登录失败", HttpStatus.NOT_FOUND);
        }
    }
	
	//增加一个授权"user:list"
    @RequiresPermissions("user:list")
    @GetMapping("/api/queryAll")
    public ResponseEntity<Object> queryAll(){
		//具体articleService的接口实现,就不在此赘述了,可自行实现
        List<Article> articles = articleService.queryAll();
        return new ResponseEntity<>(articles, HttpStatus.OK);

    }

6. 统一异常处理

我将所有shiro异常都放在GlobalExceptionHandler中

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
	//省略之前定义的其他异常
    //======================处理shiro的Exception start======================================================

    /**
     * 处理 IncorrectCredentialsException
     */
    @ExceptionHandler(value = IncorrectCredentialsException.class)
    public ResponseEntity<String> incorrectCredentialsException(IncorrectCredentialsException e) {
        // 打印堆栈信息
        log.error(ThrowableUtil.getStackTrace(e));
        return new ResponseEntity<>("密码错误", HttpStatus.NOT_FOUND);
    }


    /**
     * 处理 IncorrectCredentialsException
     */
    @ExceptionHandler(value = LockedAccountException.class)
    public ResponseEntity<String> lockedAccountException(LockedAccountException e) {
        // 打印堆栈信息
        log.error(ThrowableUtil.getStackTrace(e));
        return new ResponseEntity<>("账户已锁定", HttpStatus.NOT_FOUND);
    }


    /**
     * 处理 IncorrectCredentialsException
     */
    @ExceptionHandler(value = ExcessiveAttemptsException.class)
    public ResponseEntity<String> excessiveAttemptsException(ExcessiveAttemptsException e) {
        // 打印堆栈信息
        log.error(ThrowableUtil.getStackTrace(e));
        return new ResponseEntity<>("用户名或密码错误次数过多", HttpStatus.NOT_FOUND);
    }


    /**
     * 处理 IncorrectCredentialsException
     */
    @ExceptionHandler(value = AuthenticationException.class)
    public ResponseEntity<String> authenticationException(AuthenticationException e) {
        // 打印堆栈信息
        log.error(ThrowableUtil.getStackTrace(e));
        return new ResponseEntity<>("用户名或密码不正确", HttpStatus.NOT_FOUND);
    }
}

    //======================处理shiro请求的Exception end======================================================

shiro的自定义异常有哪些,可以在IDEA中查看源码AuthenticationException的子类:
在这里插入图片描述

7. 测试

用Postman进行测试,首先是登录
在这里插入图片描述
返回了token。此时username与password都已存入session中,再访问”http://127.0.0.1:4000/api/queryAll“
在这里插入图片描述
由于我们自己增加的是"user:show"与"user:admin",而注解要求的是"user:list",所以不符合。

8. 小结

由于我的个人博客用了redis,后续需要监听shiro中的session处理,把用户名与密码都保存到redis中,让shiro主要从redis中判断用户是否登录,同时定义token过期后,再登录跳转页面返回之前的页面而不是login页面,后续搞定后会上传笔记。shiro学起来还是挺别扭,有些类看起来不在一起,但是通过父类方法联系在一起,而且接口实现类很多,debug起来太不方便,只能一边学习一边做笔记。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值