【SaaS - Export项目】 shiro认证(防止通过链接来访问Controller 过滤器) shiro加密/加盐加密 shiro会话管理 shiro授权校验

1.Shiro认证

1.1 Shiro过滤器(拦截非法访问)

》anon代表不认证也可以访问的资源,比如静态资源,还有登录页面
》authc代表必须通过认证才可以访问,通常对动态资源(controller,jsp页面)进行拦截,如果用户没有认证,Shiro会自动跳转到login.jsp页面

 <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!--注入SecurityManager-->
        <property name="securityManager" ref="securityManager"/>
        <property name="filterChainDefinitions">
            <value>
      <!-- 对静态资源进行放行 任何人都可以访问-->
                /css/**=anon
                /img/**=anon
                /plugins/**=anon
          <!-- 任何人都可以登录,所以login-shiro.do也是匿名-->
                /system/user/login-shiro.do=anon
    <!-- authc标记的资源,只有登录认证之后才能访问 也是看session中是否有user-->
                /**=authc
            </value>
        </property>
        <!--如果 session就是没有user,表示未登录,页面转到login-shiro.jsp -->
        <property name="loginUrl" value="/login-shiro.jsp"/>

    </bean>

1.2用户密码判断

没有使用判断语句而是try/catch

使用shiro框架进行认证 结果也是三种可能 正确 没有异常,
用户不存在 UnknownAccountException,
密码出错 IncorrectCredentialsException

图解

在这里插入图片描述

UserController

 //${path}/system/user/login-shiro.do
    @RequestMapping(path = "/login-shiro", method = {RequestMethod.GET, RequestMethod.POST})
    public String loginShiro(String email,String password) {
        //根据 email查询对应的用户
        l.info("loginShiro email " + email);
        l.info("loginShiro password " + password);
        //使用shiro框架进行认证  结果也是三种可能  正确 没有异常,用户不存在 UnknownAccountException,密码出错 IncorrectCredentialsException
        //本质是需要调用realm进行查找用户
           //1:先获取subject 表示对securitymanager连接
        Subject subject = SecurityUtils.getSubject();//获取连接
      
          //2:调用 securitymanager
        UsernamePasswordToken token = new UsernamePasswordToken(email, password);//身份验证
        try {
         //3:再调用realm
            subject.login(token);//正确  --realm
            //正确
            l.info("正确");
            //保存用户信息
            //要求访问realm返回一个user对象
            User user = (User) subject.getPrincipal();//  --realm
            session.setAttribute("loginUser",user);
            //一个 Module对象 就是左侧栏上的一个菜单项
            List<Module> menus = iModuleService.findModulesByUser(user);
            session.setAttribute("menus",menus);
            l.info("login menus "+menus);
            //跳到主页
            return "redirect:/home/toMain.do";
        } catch (UnknownAccountException e) {//用户不存在
            e.printStackTrace();
            l.info("用户不存在");
            request.setAttribute("error","用户不存在");
            return "forward:/login-shiro.jsp";
        }catch (IncorrectCredentialsException e){//密码出错
            e.printStackTrace();
            l.info("密码不对");
            request.setAttribute("error","邮箱或者密码不对");
            return "forward:/login-shiro.jsp";
        }


    }

AuthRealm


//认证(登录账号密码)
    //subject.login(token);
    //参1 接受 subject.login(token); 方法的值  authenticationToken

    @Autowired
    IUserService userService;
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        l.info("AuthRealm doGetAuthenticationInfo 函数执行了");
        //响应login方法和getPrincipal方法的调用
        UsernamePasswordToken usernamePasswordToken= (UsernamePasswordToken) token;

        String email  = usernamePasswordToken.getUsername();
        l.info("doGetAuthenticationInfo --"+email);
        //调service读dao
        User user =  userService.findUserByEmail(email);
        l.info("doGetAuthenticationInfo --"+user);
        if (user == null) {
            //用户不存在
            return null;//-->系统会将null转成UnknownAccountException抛出
        } else {
            //用户存在的
            //AuthenticationInfo 返回给 User user = (User) subject.getPrincipal();
            /**
             * 参数一:principal,存放用户登录信息,subject.getPrincipal()获取
             * 参数二:数据库的密码
             * 参数三:realm的别名,只有在多个Realm的时候才会用,一般不用
             */
            AuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), "");
            return info;
        }
    }

2.加密/加盐加密

2.1MD5加密工具类MD5Utils

md5的实现


public class MD5Utils {
    //
    public static String stringToMD5(String plainText) {//参1 明文
        byte[] secretBytes = null;
        try {
            secretBytes = MessageDigest.getInstance("md5").digest(
                    plainText.getBytes());//对明文的字节进行摘要
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("没有这个md5算法!");
        }
        //16或者32
        String md5code = new BigInteger(1, secretBytes).toString(16);

        //补0 一个字节转两位的16进制
        for (int i = 0; i < 32 - md5code.length(); i++) {
            md5code = "0" + md5code;
        }
        return md5code;
    }
}

TestMd5Util


public class TestMd5Util{
//测试Md5
  @Test
    public void stringToMD5() {
        String result = MD5Utils.stringToMD5("123");
        System.out.println(result);
        //md5(123)= 202cb962ac59075b964b07152d234b70
        //202cb962ac59075b964b07152d234b70
    }
}

Shiro框架也集成了常用的加密的算法md5

Md5Hash对象

 @Test
    public void test02() {
        Md5Hash md5Hash = new Md5Hash("123");//参1 传入明文
        System.out.println(md5Hash.toString());
        //202cb962ac59075b964b07152d234b70
    }

2.2加盐(除了密码多了一个字符串,防止被别人利用彩虹表撞库来破解密码)

shiro加盐加密步骤

1、编写自定义凭证匹配器
2、在applicationContext-shiro.xml,添加自定义凭证匹配器

2.3.1 配置自定义的密码匹配器

CustomCredentialsMatcher
把表单获取的数据转为密文,再与数据库的取出的数据进行比较

/**创建自己的密码匹配器 - 通过加盐加密对用户输入的明文进行加密然后和数据库中存储的密文比较
 * 必须指定父类SimpleCredentialsMatcher
 */
public class CustomCredentialsMatcher extends SimpleCredentialsMatcher {
  @Autowired
    IUserService userService;
    
    private Logger l = LoggerFactory.getLogger(this.getClass());

    //将密码加密成密文,但需要使用账号(这里账号是邮箱)作盐
    //subject.login(token) 获取页面提交的表单数据  通过加盐加密转为密文 
    //info  调relam 查询数据库中的密码(数据库中的密码存储就是加密存储的) -> 密文2
    //通过比较密文1(用户输入的值)和密文2(数据库中的值),来判断用户输入是否正确
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        //token获取账号密码
        UsernamePasswordToken t = (UsernamePasswordToken) token;
        String email = t.getUsername(); //获取用户名(邮箱)
        //密码是char[] '1','2','3'  => "123"  new String(char[])
        String pwd1 = new String(t.getPassword());  //密码获取的是字符数组,转换为字符串
        l.info("doCredentialsMatch 用户输入的密码明文pwd1 = "+pwd1);
        //进行加盐加密,得到用户输入的密码明文加密后的密文
        Md5Hash md5Hash = new Md5Hash(pwd1, email);//参1 传入明文  参2盐
        pwd1 = md5Hash.toString();  //转换为字符串
        l.info("doCredentialsMatch 用户输入的密码加盐加密后pwd1 "+pwd1);

        //读取数据库的密码,数据库存储的就是密文
        String pwd2 = (String) info.getCredentials();
        l.info("doCredentialsMatch 数据库的密码密文pwd2 = "+pwd2);
        if (pwd1.equals(pwd2)) {
            return true;//密码正确(两个密文比较)
        } else {
            return false;//密码不正确
        }

    }
2.3.2在applicationContext-shiro.xml添加自定义的凭证匹配器(自定义密码匹配)
  <!--使用自己定义的匹配器-->
    <bean id="credentialsMatcher2" class="com.zx.web.utils.CustomCredentialsMatcher"></bean>

    <!-- 3.创建Realm(类似dao),在AuthRealm类上就不需要加@Component注解了 -->
    <bean id="authRealm" class="com.zx.web.shiro.AuthRealm">
        <!-- 使用自定义匹配器(密码) -->
        <property name="credentialsMatcher" ref="credentialsMatcher2"/>
    </bean>
2.3.3修复添加用户时对密码加盐加密

UserServiceImpl

@Override
    public void saveUser(User user) {
        String uuid= UUID.randomUUID().toString();
        user.setUserId(uuid);
        //原来保存用户使用的密码是明文,现在需要对它进行加密
        if(user.getPassword()!=null){
            Md5Hash md5Hash = new Md5Hash(user.getPassword(),user.getEmail());//参1 明文  参2 盐
            user.setPassword(md5Hash.toString());
        }
        l.info("saveUser  "+user);
        iUserDao.save(user);
    }

3.会话管理(退出功能)

退出功能就是:退出后删除session中的数据并销毁session

3.1普通退出

 @RequestMapping(path = "/loginOut-shiro", method = {RequestMethod.GET, RequestMethod.POST})
public String LoginOut(){
     //删除session中的用户信息
     session.removeAttribute("loginUser");
     //销毁session
     session.invalidate();
     return "redirect:/login.jsp";
}

3.2 Shiro退出

删除Shiro的session(底层:删除session数据)

Subject subject = SecurityUtils.getSubject();
subject.logout();

 @RequestMapping(path = "/loginOut-shiro", method = {RequestMethod.GET, RequestMethod.POST})
    public String loginOutShiro(){
        //删除session中的用户信息
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "redirect:/login-shiro.jsp";
    }

4.授权访问校验(防止已经登录的用户通过链接访问不属于自己的权限)

登陆认证后,系统校验用户是否有权限访问资源,就叫授权

4.2步骤

1 登陆认证成功后,获取用户的权限 (获取权限)
2 访问资源时候,进行授权校验:用访问资源需要的权限去用户权限列表查找,如果存在,则有权限访问资源。(权限拦截)

4.3方法

授权校验有几种方式
1) 硬编码方式(拦截方法)(非Web应用,Web应用)
2) 过滤器配置方式(拦截url)(Web应用)
3) 注解方式(拦截方法)(Web应用)
4) shiro提供的标签((拦截页面元素:按钮,表格等))(Web应用)

4.4获取权限

4.4.1查询权限sql

根据用户的登录时表单传来的邮箱进行查询用户的权限名
用到四张表 用户表 用户角色表 角色权限表 权限表

select distinct m.name from pe_user u 
inner join pe_role_user ru on ru.user_id = u.user_id
inner join pe_role_module rm on ru.role_id = rm.role_id
inner join ss_module m on m.module_id = rm.module_id
where u.email = 'lw@export.com'

4.4.2编写 查询权限的逻辑

//授权(查有什么权限)
    @Autowired
    IModuleService iModuleService;
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        l.info("AuthRealm doGetAuthorizationInfo 函数执行了");
        //获取用户信息
        Subject subject = SecurityUtils.getSubject();
        User user = (User) subject.getPrincipal();//session
        l.info("doGetAuthorizationInfo user "+user);//当前用户是谁
        if(user != null){
            //要求查询当前账号email包含哪些权限(中文表示)
            List<Module> list = iModuleService.findModulesByUser(user);
            SimpleAuthorizationInfo info =new SimpleAuthorizationInfo();
            //循环
            for(Module m:list){
                l.info("doGetAuthorizationInfo m "+m.getName());
                //将字符串表示的权限名称添加到shiro
                info.addStringPermission( m.getName());
            }//end for
            return info;
        }//end if
        return null;
    }

4.5具体的方法

4.5.1访问代码 硬编码(不推荐)

在每一需要对应权限才能访问的功能方法中写出所需要的权限(用户没有这个权限就不能访问)
写在Controller的功能方法中
例子:如果该用户没有企业管理权限则无法通过链接直接访问该功能

Subject subject = SecurityUtils.getSubject();
subject.checkPermission("企业管理");

4.5.2XML配置方式实现权限管理(推荐)

1)原理:shiro过滤器
拦截请求时,获取授权

2)在applicationContext-shiro.xml
toList.do方法只有用户拥有企业管理或者用户管理权限时才能访问

<!-- 给url配置权限-->
 /company/toList.do=perms["企业管理"]     
 /system/user/toList.do=perms["用户管理"]

3)可以指定 无权限时的页面提示

在这里插入图片描述

4.5.3shiro授权 注解方式实现

4.5.4shiro授权 注解方式实现

1 在applicationContext-shiro.xml中开启shiro注解支持

 <!-- @RequiredPermission-->
    <bean id="lifecycleBeanPostProcessor"
          class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
          depends-on="lifecycleBeanPostProcessor"/>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

2.开启Aop自动代理

<aop:aspectj-autoproxy/>

3.在controller中的方法上使用

@RequiresPermissions("企业管理")

4.3.5shiro授权 Shiro标签实现权限管理

用在jsp页面
1 在页面引入shiro标签 ,类似c标签
<%@ taglib prefix=“shiro” uri=“http://shiro.apache.org/tags” %>
2 使用shiro标签 给name赋值,赋上什么值就表示需要什么权限
标签会查询权限,如果有权限,就对应的权限页面显示,如果没有权限不报错,将页面内容进行隐藏

<%--        标签会查询权限,如果没有权限不报错,将页面内容进行隐藏,反之显示页面内容--%>

        <shiro:hasPermission name="企业管理">
            <a href="">企业管理</a>
        </shiro:hasPermission>

        <shiro:hasPermission name="用户管理">
            <a href="">用户管理</a>
        </shiro:hasPermission>

        <shiro:hasPermission name="日志管理">
            <a href="">日志管理</a>
        </shiro:hasPermission>

5.小结

无论是认证还是授权,都是要先进行连接,才能对安全管理器进行访问,并且通过安全管理器来访问realm

 Subject subject = SecurityUtils.getSubject();

认证

在这里插入图片描述

授权

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值