当前位置: 移动技术网 > IT编程>开发语言>Java > shiro,基于springboot,基于前后端分离,从登录认证到鉴权,从入门到放弃

shiro,基于springboot,基于前后端分离,从登录认证到鉴权,从入门到放弃

2018年11月08日  | 移动技术网IT编程  | 我要评论

这个demo是基于springboot项目的。

名词介绍:

shiro
shiro 主要分为 安全认证 和 接口授权 两个部分,其中的核心组件为 subject、 securitymanager、 realms,公共部分 shiro 都已经为我们封装好了,我们只需要按照一定的规则去编写响应的代码即可…

subject 即表示主体,将用户的概念理解为当前操作的主体,因为它即可以是一个通过浏览器请求的用户,也可能是一个运行的程序,外部应用与 subject 进行交互,记录当前操作用户。subject 代表了当前用户的安全操作,securitymanager 则管理所有用户的安全操作。

securitymanager 即安全管理器,对所有的 subject 进行安全管理,并通过它来提供安全管理的各种服务(认证、授权等)

realm 充当了应用与数据安全间的 桥梁 或 连接器。当对用户执行认证(登录)和授权(访问控制)验证时,shiro 会从应用配置的 realm 中查找用户及其权限信息。

1.导入shiro依赖

        <dependency>
            <groupid>org.apache.shiro</groupid>
            <artifactid>shiro-spring</artifactid>
            <version>1.4.0</version>
        </dependency>
    
        <dependency>
            <groupid>org.crazycake</groupid>
            <artifactid>shiro-redis</artifactid>
            <version>2.8.24</version>
        </dependency>
shiro-redis为什么要导入这个包呢?将用户信息交给redis管理。

2.shiro配置类

package com.test.cbd.shiro;


import org.apache.shiro.authc.credential.hashedcredentialsmatcher;
import org.apache.shiro.mgt.securitymanager;
import org.apache.shiro.session.mgt.sessionmanager;
import org.apache.shiro.spring.security.interceptor.authorizationattributesourceadvisor;
import org.apache.shiro.spring.web.shirofilterfactorybean;
import org.apache.shiro.web.mgt.defaultwebsecuritymanager;
import org.crazycake.shiro.rediscachemanager;
import org.crazycake.shiro.redismanager;
import org.crazycake.shiro.redissessiondao;
import org.springframework.aop.framework.autoproxy.defaultadvisorautoproxycreator;
import org.springframework.beans.factory.annotation.value;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.web.servlet.handlerexceptionresolver;
import java.util.linkedhashmap;
import java.util.map;

@configuration
public class shiroconfig {

    @value("${spring.redis.shiro.host}")
    private string host;
    @value("${spring.redis.shiro.port}")
    private int port;
    @value("${spring.redis.shiro.timeout}")
    private int timeout;
    @value("${spring.redis.shiro.password}")
    private string password;

    @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>();
        //注意过滤器配置顺序 不能颠倒
        //配置退出 过滤器,其中的具体的退出代码shiro已经替我们实现了,登出后跳转配置的loginurl
        filterchaindefinitionmap.put("/logout", "logout");
        // 配置不会被拦截的链接 顺序判断,在 shiroconfiguration 中的 shirofilter 处配置了 /ajaxlogin=anon,意味着可以不需要认证也可以访问
        filterchaindefinitionmap.put("/static/**", "anon");
        filterchaindefinitionmap.put("/*.html", "anon");
        filterchaindefinitionmap.put("/ajaxlogin", "anon");
        filterchaindefinitionmap.put("/login", "anon");
        filterchaindefinitionmap.put("/**", "authc");
        //配置shiro默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据
        shirofilterfactorybean.setloginurl("/unauth");
        // 登录成功后要跳转的链接
//        shirofilterfactorybean.setsuccessurl("/index");
        //未授权界面;
//        shirofilterfactorybean.setunauthorizedurl("/403");
        shirofilterfactorybean.setfilterchaindefinitionmap(filterchaindefinitionmap);
        return shirofilterfactorybean;
    }

    /**
     * 凭证匹配器
     * (由于我们的密码校验交给shiro的simpleauthenticationinfo进行处理了
     * )
     *
     * @return
     */
    @bean
    public hashedcredentialsmatcher hashedcredentialsmatcher() {
        hashedcredentialsmatcher hashedcredentialsmatcher = new hashedcredentialsmatcher();
        hashedcredentialsmatcher.sethashalgorithmname("md5");//散列算法:这里使用md5算法;
        hashedcredentialsmatcher.sethashiterations(1024);//散列的次数,比如散列两次,相当于 md5(md5(""));
        return hashedcredentialsmatcher;
    }

    @bean
    public myshirorealm myshirorealm() {
        myshirorealm myshirorealm = new myshirorealm();
        myshirorealm.setcredentialsmatcher(hashedcredentialsmatcher());
        return myshirorealm;
    }


    @bean
    public securitymanager securitymanager() {
        defaultwebsecuritymanager securitymanager = new defaultwebsecuritymanager();
        securitymanager.setrealm(myshirorealm());
        // 自定义session管理 使用redis
        securitymanager.setsessionmanager(sessionmanager());
        // 自定义缓存实现 使用redis
        securitymanager.setcachemanager(cachemanager());
        return securitymanager;
    }

    //自定义sessionmanager
    @bean
    public sessionmanager sessionmanager() {
        mysessionmanager mysessionmanager = new mysessionmanager();
        mysessionmanager.setsessiondao(redissessiondao());
        return mysessionmanager;
    }

    /**
     * 配置shiro redismanager
     * <p>
     * 使用的是shiro-redis开源插件
     *
     * @return
     */
    public redismanager redismanager() {
        redismanager redismanager = new redismanager();
        redismanager.sethost(host);
        redismanager.setport(port);
        redismanager.setexpire(1800);// 配置缓存过期时间
        redismanager.settimeout(timeout);
        redismanager.setpassword(password);
        return redismanager;
    }

    /**
     * cachemanager 缓存 redis实现
     * <p>
     * 使用的是shiro-redis开源插件
     *
     * @return
     */
    @bean
    public rediscachemanager cachemanager() {
        rediscachemanager rediscachemanager = new rediscachemanager();
        rediscachemanager.setredismanager(redismanager());
        return rediscachemanager;
    }

    /**
     * redissessiondao shiro sessiondao层的实现 通过redis
     * <p>
     * 使用的是shiro-redis开源插件
     */
    @bean
    public redissessiondao redissessiondao() {
        redissessiondao redissessiondao = new redissessiondao();
        redissessiondao.setredismanager(redismanager());
        return redissessiondao;
    }

    /**
     * 开启shiro aop注解支持.
     * 使用代理方式;所以需要开启代码支持;
     *
     * @param securitymanager
     * @return
     */
    @bean
    public authorizationattributesourceadvisor authorizationattributesourceadvisor(securitymanager securitymanager) {
        authorizationattributesourceadvisor authorizationattributesourceadvisor = new authorizationattributesourceadvisor();
        authorizationattributesourceadvisor.setsecuritymanager(securitymanager);
        return authorizationattributesourceadvisor;
    }

    /**
     * 注册全局异常处理
     * @return
     */
    @bean(name = "exceptionhandler")
    public handlerexceptionresolver handlerexceptionresolver() {
        return new myexceptionhandler();
    }

    //自动创建代理,没有这个鉴权可能会出错
    @bean
    public defaultadvisorautoproxycreator getdefaultadvisorautoproxycreator() {
        defaultadvisorautoproxycreator autoproxycreator = new defaultadvisorautoproxycreator();
        autoproxycreator.setproxytargetclass(true);
        return autoproxycreator;
    }
}

3.安全认证和权限验证的核心,自定义realm 

package com.test.cbd.shiro;


import com.google.gson.jsonobject;
import com.test.cbd.service.userservice;
import com.test.cbd.vo.syspermission;
import com.test.cbd.vo.sysrole;
import com.test.cbd.vo.userinfo;
import org.apache.commons.beanutils.beanutils;
import org.apache.shiro.securityutils;
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.session.session;
import org.apache.shiro.subject.principalcollection;
import org.apache.shiro.subject.subject;
import org.apache.shiro.util.bytesource;
import springfox.documentation.spring.web.json.json;

import javax.annotation.resource;
import java.util.hashset;
import java.util.set;


public class myshirorealm extends authorizingrealm {
    @resource
    private userservice userinfoservice;

    @override
    protected authorizationinfo dogetauthorizationinfo(principalcollection principals){
//        // 权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)
        simpleauthorizationinfo authorizationinfo = new simpleauthorizationinfo();
        session session = securityutils.getsubject().getsession();
        userinfo user = (userinfo) session.getattribute("user_session");
        // 用户的角色集合
        set<string> roles = new hashset<>();
        roles.add(user.getrolelist().get(0).getrole());
        authorizationinfo.setroles(roles);
        return authorizationinfo;
    }

    /*主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。*/
    @override
    protected authenticationinfo dogetauthenticationinfo(authenticationtoken token)
            throws authenticationexception {
//        system.out.println("myshirorealm.dogetauthenticationinfo()");
        //获取用户的输入的账号.
        string username = (string) token.getprincipal();
//        system.out.println(token.getcredentials());
        //通过username从数据库中查找 user对象,如果找到,没找到.
        //实际项目中,这里可以根据实际情况做缓存,如果不做,shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
        userinfo userinfo = userinfoservice.findbyusername(username);
       // subject subject = securityutils.getsubject();
        //session session = subject.getsession();
        //session.setattribute("role",userinfo.getrolelist());
//        system.out.println("----->>userinfo="+userinfo);
        if (userinfo == null) {
            return null;
        }
        if (userinfo.getstate() == 1) { //账户冻结
            throw new lockedaccountexception();
        }
        string credentials = userinfo.getpassword();
        system.out.println("credentials="+credentials);
        bytesource credentialssalt = bytesource.util.bytes(username);
        simpleauthenticationinfo authenticationinfo = new simpleauthenticationinfo(
                userinfo.getusername(), //用户名
                credentials, //密码
                credentialssalt,
                getname()  //realm name
        );
        session session = securityutils.getsubject().getsession();
        session.setattribute("user_session", userinfo);
        return authenticationinfo;
    }

}

4.全局异常处理器

package com.test.cbd.shiro;


import com.alibaba.fastjson.support.spring.fastjsonjsonview;
import org.apache.shiro.authz.unauthenticatedexception;
import org.apache.shiro.authz.unauthorizedexception;
import org.springframework.web.servlet.handlerexceptionresolver;
import org.springframework.web.servlet.modelandview;
import javax.servlet.http.httpservletrequest;
import javax.servlet.http.httpservletresponse;
import java.util.hashmap;
import java.util.map;

/**
 * created by administrator on 2017/12/11.
 * 全局异常处理
 */
public class myexceptionhandler implements handlerexceptionresolver {

    public modelandview resolveexception(httpservletrequest httpservletrequest, 
            httpservletresponse httpservletresponse, object o, exception ex) { modelandview mv = new modelandview(); fastjsonjsonview view = new fastjsonjsonview(); map<string, object> attributes = new hashmap<string, object>(); if (ex instanceof unauthenticatedexception) { attributes.put("code", "1000001"); attributes.put("msg", "token错误"); } else if (ex instanceof unauthorizedexception) { attributes.put("code", "1000002"); attributes.put("msg", "用户无权限"); } else { attributes.put("code", "1000003"); attributes.put("msg", ex.getmessage()); } view.setattributesmap(attributes); mv.setview(view); return mv; } }

5.因为现在的项目大多都是前后端分离的,所以我们需要实现自己的session管理

package com.test.cbd.shiro;

import org.apache.shiro.web.servlet.shirohttpservletrequest;
import org.apache.shiro.web.session.mgt.defaultwebsessionmanager;
import org.apache.shiro.web.util.webutils;
import org.springframework.util.stringutils;
import javax.servlet.servletrequest;
import javax.servlet.servletresponse;
import java.io.serializable;


public class mysessionmanager extends defaultwebsessionmanager {

    private static final string token = "token";

    private static final string referenced_session_id_source = "stateless request";

    public mysessionmanager() {
        super();
    }

    @override
    protected serializable getsessionid(servletrequest request, servletresponse response) {
        string id = webutils.tohttp(request).getheader(token);
        //如果请求头中有 token 则其值为sessionid
        if (!stringutils.isempty(id)) {
            request.setattribute(shirohttpservletrequest.referenced_session_id_source, referenced_session_id_source);
            request.setattribute(shirohttpservletrequest.referenced_session_id, id);
            request.setattribute(shirohttpservletrequest.referenced_session_id_is_valid, boolean.true);
            return id;
        } else {
            //否则按默认规则从cookie取sessionid
            return super.getsessionid(request, response);
        }
    }
}

6.控制器

package com.test.cbd.shiro;

import com.alibaba.fastjson.jsonobject;
import com.test.cbd.vo.userinfo;
import io.swagger.annotations.api;
import lombok.extern.slf4j.slf4j;
import org.apache.shiro.securityutils;
import org.apache.shiro.authc.authenticationexception;
import org.apache.shiro.authc.incorrectcredentialsexception;
import org.apache.shiro.authc.lockedaccountexception;
import org.apache.shiro.authc.usernamepasswordtoken;
import org.apache.shiro.authz.annotation.requiresroles;
import org.apache.shiro.crypto.hash.simplehash;
import org.apache.shiro.session.session;
import org.apache.shiro.subject.subject;
import org.apache.shiro.util.bytesource;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.httpservletrequest;
import java.net.inetaddress;

@slf4j
@api(value="shiro测试",description="shiro测试")
@restcontroller
@requestmapping("/")
public class shirologincontroller {

    /**
     * 登录测试
     * @param userinfo
     * @return
     */
    @requestmapping(value = "/ajaxlogin", method = requestmethod.post)
    @responsebody
    public string ajaxlogin(userinfo userinfo) {
        jsonobject jsonobject = new jsonobject();
        usernamepasswordtoken token = new usernamepasswordtoken(userinfo.getusername(), userinfo.getpassword());
        subject subject = securityutils.getsubject();
        try {
            subject.login(token);
            jsonobject.put("token", subject.getsession().getid());
            jsonobject.put("msg", "登录成功");
        } catch (incorrectcredentialsexception e) {
            jsonobject.put("msg", "密码错误");
        } catch (lockedaccountexception e) {
            jsonobject.put("msg", "登录失败,该用户已被冻结");
        } catch (authenticationexception e) {
            jsonobject.put("msg", "该用户不存在");
        } catch (exception e) {
            e.printstacktrace();
        }
        return jsonobject.tostring();
    }

    /**
     * 鉴权测试
     * @param userinfo
     * @return
     */
    @requestmapping(value = "/check", method = requestmethod.get)
    @responsebody
    @requiresroles("guest")
    public string check() {
        jsonobject jsonobject = new jsonobject();
        jsonobject.put("msg", "鉴权测试");
        return jsonobject.tostring();
    }
}

常用注解
@requiresguest 代表无需认证即可访问,同理的就是 /path=anon

@requiresauthentication 需要认证,只要登录成功后就允许你操作

@requirespermissions 需要特定的权限,没有则抛出 authorizationexception

@requiresroles 需要特定的橘色,没有则抛出 authorizationexception


7.以上就是shiro登陆和鉴权的主要配置和类,下面补充一下其他信息。

①application.properties中shiro-redis相关配置:

spring.redis.shiro.host=127.0.0.1
spring.redis.shiro.port=6379
spring.redis.shiro.timeout=5000
spring.redis.shiro.password=123456

②因为数据是模拟的,所以在登陆认证的时候,并没有通过数据库查用户信息,可以通过以下方式模拟加密后的密码:

    /**
     * 生成测试用的md5加密的密码
     * @param args
     */
    public static void main(string[] args) {
        string hashalgorithmname = "md5";
        string credentials = "123456";//密码
        int hashiterations = 1024;
        bytesource credentialssalt = bytesource.util.bytes("root");//账号
        string  obj = new simplehash(hashalgorithmname, credentials, credentialssalt, hashiterations).tohex();
        system.out.println(obj);
    }

上面obj的结果是b1ba853525d0f30afe59d2d005aad96c

③登陆认证的findbyusername方法,模拟到数据库查询用户信息,实际是自己构造的数据,偷偷懒。

    public userinfo findbyusername(string username){
        sysrole admin = sysrole.builder().role("admin").build();
        list<syspermission> list=new arraylist<syspermission>();
        syspermission syspermission=new syspermission("read");
        syspermission syspermission1=new syspermission("write");
        list.add(syspermission);
        list.add(syspermission1);
        admin.setpermissions(list);
        userinfo root = userinfo.builder().username("root").password("b1ba853525d0f30afe59d2d005aad96c").credentialssalt("123").state(0).build();
        list<sysrole> rolelist=new arraylist<sysrole>();
        rolelist.add(admin);
        root.setrolelist(rolelist);
        return root;
    }

8.结果演示

输入正确的账号密码时,返回信息如下:

 

 故意输错密码时,返回信息如下:

 

鉴权时,如果该用户没有角色:

完!

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网