Hello shiro+springmvc实例--shiro加强篇

转载请注明出处:
牵手生活--:笔记是整理思路方式,分享是一个美德,牵手是我的生活方式

注:此文承接上一文:Hello shiro基础知识整理---shiro基础篇下面开始我们的工作

知识要点:

  • spring + springmvc
  • shiro(自定义realm、SecurityManager环境、HashedCredentialsMatcher加密、>提交认证授权请求subject)
  • spring jdbc访问数据库
  • 阿里数据源Druid(DruidDataSource)
  • spring 的aop操作需要引入aspectj切面框架支持的包aspectjweaver
  • shiro自定义过滤器
  • Redis知识及Redis的访问工具包Jedis
  • 反序列化SerializationUtils.deserialize方法&序列化SerializationUtils.serialize
  • Redis桌面管理工具Redis Desktop Manager

shiro授权过程

Hello shiro+springmvc实例--shiro加强篇_第1张图片
shiro授权过程

shiro知识简图

Hello shiro+springmvc实例--shiro加强篇_第2张图片
shiro知识简图

Shiro集成Spring

Shiro集成Spring

配置maven web工程的pom.xml,添加对spring mvc和shiro的支持



      
    
      org.springframework
      spring-context
        ${spring.version}
    
    
      
          org.springframework
          spring-webmvc
          ${spring.version}
      


    
    
    
      org.apache.shiro
      shiro-core
      1.4.0
    

    

    
      org.apache.shiro
      shiro-spring
      1.4.0
    
    
    
      org.apache.shiro
      shiro-web
      1.4.0
    

配置maven的web工程的web.xml




  Archetype Created Web Application

    
  
    shiroFilter
    org.springframework.web.filter.DelegatingFilterProxy
  
  
    shiroFilter
    /*
  


  
  
    contextConfigLocation
    classpath*:spring/spring.xml
  

  
    
      org.springframework.web.context.ContextLoaderListener
    
  


  
  
    mvc-dispatcher
     org.springframework.web.servlet.DispatcherServlet
    
    
      contextConfigLocation
      classpath*:/spring/springmvc.xml
    
    1
  
  
    mvc-dispatcher
    
    /
  




  



集成Shiro 和Springmvc后的目录结构
Hello shiro+springmvc实例--shiro加强篇_第3张图片
集成Shiro 和Springmvc后的目录结构
Spring的配置文件Spring.xml(shiro的Filter、SecurityManager、自定义realm、加密设置HashedCredentialsMatcher)




    
    

    
        
        
        
        
        
        
            
                /login.html = anon
                /subLogin = anon
                /* = authc
            

        
        
        
    

    
    
        
        

    
    
    
        
        
        
    

    
    
        
        
        
        
        
    


SpringMVC的配置文件springmvc.xml



    
    

    
    
    


    
    
    
    

    
    
    


创建springmvc中的Controller

@Controller /*注解这是一个Controller*/
public class UserController {

    @RequestMapping(value = "/subLogin",method = RequestMethod.POST,
    produces = "application/json;charset=utf-8")
    @ResponseBody
    public String subLogin(User user){//通过实体对象传递参数
        /*获得主体*/
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken taken = new UsernamePasswordToken(user.getUsername(),user.getPassword());

        try {
            subject.login(taken);
        } catch (AuthenticationException e) {
            //e.printStackTrace();
            return e.getMessage();
        }

        return "登录成功";
        
    }
}

部署并运行web项目

url(用户名:younghare;密码:123456)

http://localhost:8080/login.html
Hello shiro+springmvc实例--shiro加强篇_第4张图片
登录界面

登录成功界面


Hello shiro+springmvc实例--shiro加强篇_第5张图片
登录成功界面

登录失败界面
提示:Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - younghare, rememberMe=false] did not match the expected credentials.

Hello shiro+springmvc实例--shiro加强篇_第6张图片
登录失败界面

Shiro集成Spring 从数据库中获取数据对象(spring-jdbc、数据源DruidDataSource)

配置pom.xml(添加mysql驱动包、druid数据源、spring-jdbc)

spring核心包在前面已经导入了

      
      
          com.alibaba
          druid
          1.1.6
      
      
      
          mysql
          mysql-connector-java
          6.0.6
      

    

      
          org.springframework
          spring-jdbc
          ${spring.version}
      

创建spring jdbc的配置文件spring-jdbc.xml



    
    
        
        
        

    

    
    
        
        
    


修改Spring.xml(引入Spring-jdbc.xml配置文件、设置扫描路径)
    
    
    
    

Hello shiro+springmvc实例--shiro加强篇_第7张图片
引入其他Spring配置文件

创建Dao,DaoImpl、修改Controller、修改realm后的结构

Hello shiro+springmvc实例--shiro加强篇_第8张图片
创建Dao,DaoImpl、修改Controller、修改realm后的结构
UserDao(访问数据库)
public interface UserDao {
    User getUserByUsername(String userName);

    List queryRolesByUserName(String userName);
}

UserDaoImpl(访问数据库实现类)
package com.younghare.dao.impl;

import com.younghare.dao.UserDao;
import com.younghare.vo.User;
import org.apache.shiro.util.CollectionUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

@Component /*让他实现成对象*/
public class UserDaoImpl implements UserDao{
    @Resource/*注入jdbcTemplate*/
    private JdbcTemplate jdbcTemplate;
    @Override
    public User getUserByUsername(String userName) {
        String sql = "select username,password from users where username = ?";
        List list = jdbcTemplate.query(sql, new String[]{userName}, new RowMapper() {
            @Override
            public User mapRow(ResultSet resultSet, int i) throws SQLException {
                User user = new User();
                user.setUsername(resultSet.getString("username"));
                user.setPassword(resultSet.getString("password"));
                return user;
            }
        });

        if (CollectionUtils.isEmpty(list)){
            return null;
            
        }
        return list.get(0); //直接返回第一条
    }

    @Override
    public List queryRolesByUserName(String userName) {
        String sql = "select role_name from test_user_role where user_name = ?";
        return  jdbcTemplate.query(sql, new String[]{userName}, new RowMapper() {
            @Override
            public String mapRow(ResultSet resultSet, int i) throws SQLException {
                return resultSet.getString("role_name");
            }
        });

    }
}

修改CustomRealm

public class CustomRealm extends AuthorizingRealm {

    @Resource /*注入userDao*/
    private UserDao userDao;//用户的数据库信息


    @Override //授权
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String userName = (String) principalCollection.getPrimaryPrincipal();
        //从数据库或缓冲中获得角色数据
        Set roles = getRolesByUserName(userName);
        //从数据库或缓冲中获得权限数据
        Set permissions = getPermissionsByUserName(userName);

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setStringPermissions(permissions);
        simpleAuthorizationInfo.setRoles(roles);

        return simpleAuthorizationInfo;
    }

    /**
     * 模拟用户权限数据
     * @param userName
     * @return
     */
    private Set getPermissionsByUserName(String userName) {
        Set sets = new HashSet<>();
        sets.add("user:myAdd");
        sets.add("user:delete");
        sets.add("user:update");
        return sets;

    }

    /**
     * 角色数据
     * @param userName
     * @return
     */
    private Set getRolesByUserName(String userName) {

        List list = userDao.queryRolesByUserName(userName);
        Set sets = new HashSet<>(list);

        return sets;
    }

    @Override //认证
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        /*从主体传来的认证信息中,获得用户名*/
        String userName = (String) authenticationToken.getPrincipal();
        /*通过用户名到数据库中获取凭证*/
        String password = getPasswordByUserName(userName);
        if (password == null){
            return null;
        }

        SimpleAuthenticationInfo authenticationInfo
                = new SimpleAuthenticationInfo(userName,password,"customRealm");

        //如果有对shiro加密并加盐了,则需要添加加盐的处理
        authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(userName));

        return authenticationInfo;
    }

    /**
     * 数据库操作
     * @param userName
     * @return
     */
    private String getPasswordByUserName(String userName) {
        /*通过Dao到数据库中获取用户信息*/
        User user = userDao.getUserByUsername(userName);
        if (user !=null){
            System.out.println(userName+":"+user.getPassword());
            return user.getPassword();

        }

        return null;
    }


}

修改UserController

@Controller /*注解这是一个Controller*/
public class UserController {

    @RequestMapping(value = "/subLogin",method = RequestMethod.POST,
    produces = "application/json;charset=utf-8")
    @ResponseBody
    public String subLogin(User user){//通过实体对象传递参数
        /*获得主体*/
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken taken = new UsernamePasswordToken(user.getUsername(),user.getPassword());

        try {
            subject.login(taken);
        } catch (AuthenticationException e) {
            //e.printStackTrace();
            return e.getMessage();
        }

        if (subject.hasRole("admin")){
            return "登录成功--有admin权限***";
        }

        return "登录成功-无admin权限";
        
    }
}

重新部署web应用,并登陆的效果
Hello shiro+springmvc实例--shiro加强篇_第9张图片
成功登录

失败的效果没有变化

通过注解配置授权(aspectjweaver包)

修改pom.xml(引入aspectjweaver包)
      
      
          org.aspectj
          aspectjweaver
          1.8.9
      
修改spring.xml(添加对aspectjweaver的支持)
    
    
    
        
    
    
    
        
        
    
Hello shiro+springmvc实例--shiro加强篇_第10张图片
添加对aspectjweaver支持
为UserController.java 添加2个方法(注解的方式配置授权)

    @RequiresRoles("admin") /*表示当前的主体必须具备admin权限才能访问,数组可以传入多个参数*/
    @RequestMapping(value = "/testRole",method = RequestMethod.GET)
    @ResponseBody /*信息放回,http 访问或ajax访问*/
    public String testRole(){
        return "testRole success";
    }

    @RequiresRoles("admin1") /*表示当前的主体必须具备admin权限才能访问*/
    @RequiresPermissions("user:add") /*表示必须具备对user的add权限*/
    @RequestMapping(value = "/testRole1",method = RequestMethod.GET)
    @ResponseBody /*信息放回,http 访问或ajax访问*/
    public String testRole1(){
        return "testRole success";
    }

用younghare用户登录成功后,在浏览器中直接访问

http://localhost:8080/testRole

问题:Caused by: java.lang.NoClassDefFoundError: org/aspectj/util/PartialOrder$PartialComparable
解决办法:原来是我的2个方法都是 @RequestMapping(value = "/testRole",method = RequestMethod.GET)

Hello shiro+springmvc实例--shiro加强篇_第11张图片
测试testRole结果

Shiro过滤器与自定义过滤器

认证相关的过滤器

  • anon:不需要任何认证
  • authBasis
  • authc:认证之后才可以访问
  • user:需要当前存在用户才可以访问
  • logout:退出

授权相关的过滤器:

  • perms:具备相关的一些权限才可以进行访问"["+参数+"[]"
  • roles:同时具备相关的一些角色才可以进行访问"["+参数+"[]"
  • rolesOr:只要具备相关的角色的一个就可以进行访问"["+参数+"[]"
  • ssl:要求安全的协议https
  • port:要求端口"[]"中写的是端口
在Spring.xml中配置shiro的过滤器
    
        
        
        
        
        
        
            
                /login.html = anon
                /subLogin = anon
                /testMyRoles = roles["admin"]
                /testMyRoles2 = roles["admin","admin1]
                /testPerms = perms["user:delete"]
                /testPerms2 = perms["user:delete","user:update"]
                /* = authc
            

                
        
    
Hello shiro+springmvc实例--shiro加强篇_第12张图片
在Spring.xml中配置shiro的过滤器
Hello shiro+springmvc实例--shiro加强篇_第13张图片
image.png

shiro自定义filter过滤器

创建shiro自定义filter过滤器RolesOrFilter.java

需要继承的filter来源


Hello shiro+springmvc实例--shiro加强篇_第14张图片
需要继承的filter来源

创建自定义过滤器RolesOrFilter.java


public class RolesOrFilter extends AuthorizationFilter {
    @Override
    protected boolean isAccessAllowed(javax.servlet.ServletRequest servletRequest
            , javax.servlet.ServletResponse servletResponse, Object object) throws Exception {
        
        /*获得当前的主体*/
        Subject subject = getSubject(servletRequest,servletResponse);
        String[] roles = (String[]) object;
        if (roles == null || roles.length == 0){
            return true;
        }
        for (String role :roles){
            if (subject.hasRole(role)){
                return  true;
            }
        }

        return false;
    }
}
在spring.xml的配置文件中配置自定义过滤器

    
        
        
        
        
        
        
            
                /login.html = anon
                /subLogin = anon
                /testMyRoles = roles["admin","admin1"]
                /testMyRoles2 = rolesOr["admin","admin1"]
                /* = authc
            
        
        
            
                
                
            
        
        
        
    

    
    
Hello shiro+springmvc实例--shiro加强篇_第15张图片
自定义shiro 的filter配置
重新部署web,并按younghare登录(具有admin,user角色)

登录成功后访问url

http://localhost:8080/login.html
http://localhost:8080/testMyRoles

![必须登录成功](https://upload-images.jianshu.io/upload_images/5438896-
767bdeefd5af93ba.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

Hello shiro+springmvc实例--shiro加强篇_第16张图片
rolesOr拥有角色访问的情况
Hello shiro+springmvc实例--shiro加强篇_第17张图片
roles同时拥有角色

Hello shiro+springmvc实例--shiro加强篇_第18张图片
image.png
补上我们的403.html资源文件即可

省略

Shiro会话管理(session)和缓冲管理

Shiro会话管理

在pom.xml manen配置文件中添加redis的访问工具jedis
    
    
      redis.clients
      jedis
      2.9.0
      jar
      compile
    
通过Redis访问工具,需要修改增删改查的操作。创建RedisSessionDao并继承AbstractSessionDAO,同时实现对应的抽象方法
创建spring 配置文件管理spring-redis.xml配置文件




    
    
        
        

        
         
        
         
    
    
    


修改spring.xml(引入spring-redis.xml)

    
    
创建访问Redis的工具类JedisUtil

JedisUtil.java

/**
 * Redis的访问工具包,主要是Jedis的一些操作方法
 */

@Component
public class JedisUtil {
    //JedisPool jedisPool = new JedisPool(config,"127.0.0.1",6379);
    @Autowired /*自动注入与@Resource注入的区别*/
    private JedisPool jedisPool;

    /*放回获取连接Redis的方法*/
    private Jedis getResource(){
        return jedisPool.getResource();
    }

    public byte[] set(byte[] key, byte[] value) {
        Jedis jedis = getResource();

        try {
            jedis.set(key,value);
            return  value;
        } finally {
            jedis.close();
        }

        
    }

    /**
     *
     * @param key
     * @param expire 设置过期时间,单位秒
     */
    public void expire(byte[] key, int expire) {
        Jedis jedis = getResource();

        try {
            jedis.expire(key,expire);
        } finally {
            jedis.close();
        }
    }

    public byte[] get(byte[] key) {
        Jedis jedis = getResource();

        try {
            return jedis.get(key);
        } finally {
            jedis.close();
        }
    }

    public void del(byte[] key) {
        Jedis jedis = getResource();

        try {
            jedis.del(key);
        } finally {
            jedis.close();
        }
    }

    public Set keys(String shiro_session_prefix) {
        Jedis jedis = getResource();

        try {
            return jedis.keys((shiro_session_prefix+"*").getBytes());
        } finally {
            jedis.close();
        }
    }
}
创建RedisSessionDao用于管理session,继承AbstractSessionDAO并实现对应的抽象方法

RedisSessionDao.java


public class RedisSessionDao extends AbstractSessionDAO {
    
    @Resource /*注入jedisUtil对象*/
    private JedisUtil jedisUtil;
    /*定义session的前缀*/
    private  final String shiro_session_prefix ="younghare-session";
    private byte[] getKey(String key){
        return (shiro_session_prefix+key).getBytes();
    }

    //保存session
    private void saveSession(Session session){
        if (session !=null && session.getId() !=null){
            byte[] key = getKey(session.getId().toString());
            /*序列化后才去保存,读取时需要反序列化回来*/
            byte[] value = SerializationUtils.serialize(session);
            jedisUtil.set(key,value);//保存到Redis中
            jedisUtil.expire(key,10*60); //过期时间时间10分钟

        }

    }

    //创建session
    @Override
    protected Serializable doCreate(Session session) {
        /*获得sessionId*/
        Serializable sesssionId = generateSessionId(session);
        saveSession(session);

        return sesssionId;
    }

    //获得session
    @Override
    protected Session doReadSession(Serializable sessionId) {
        if (sessionId == null){
            return null;
        }
        byte[] key = getKey(sessionId.toString());
        System.out.println("读取session:"+new String(key));
        byte[] value = jedisUtil.get(key);
        /*返回系列化对象*/
        return (Session) SerializationUtils.deserialize(value);

    }

    //更新sesssion
    @Override
    public void update(Session session) throws UnknownSessionException {
        saveSession(session);

    }

    //删除session
    @Override
    public void delete(Session session) {
        if (session == null || session.getId() == null){
            return;
        }

        byte[] key = getKey(session.getId().toString());
        jedisUtil.del(key);

    }

    //获得到指定存活的session
    @Override
    public Collection getActiveSessions() {
        /*通过前缀获取所以的key值*/
        Set keys = jedisUtil.keys(shiro_session_prefix);

        Set sessions = new HashSet<>();
        if (CollectionUtils.isEmpty(keys)){
            return sessions;
        }

        for(byte[] key:keys){
            /*反序列化为Session对象*/
            Session session = (Session) SerializationUtils.deserialize(jedisUtil.get(key));
            sessions.add(session);
            
        }
        

        return sessions;
    }
}

修改Spring.xml(创建DefaultWebSessionManager对象,并配置到自定义的RedisSessionDao对象)
    
    
        
        
        

    
    
    

修改spring.xml(在DefaultWebSecurityManager设置session管理对象)
Hello shiro+springmvc实例--shiro加强篇_第19张图片
在DefaultWebSecurityManager设置session管理对象
    
    
        
        
        
        

    
重新部署运行web(注意要驱动Redis服务)

用younghare登录,访问之前的一些url

//登录
http://localhost:8080/login.html
//角色测试url2
http://localhost:8080/testMyRoles2
//角色测试url
http://www.localhost.com:8080/testMyRoles
用Redis桌面管理工具Redis Desktop Manager连接查看
Hello shiro+springmvc实例--shiro加强篇_第20张图片
image.png
Hello shiro+springmvc实例--shiro加强篇_第21张图片
多个浏览器访问的情况

日志情况


Hello shiro+springmvc实例--shiro加强篇_第22张图片
日志情况
doReadSession被多次调用的优化

问题:根据日志情况发现一次访问,会调用多次的doReadSession
解决:查看DefaultWebSessionManager的父类DefaultSessionManager的retrieveSession方法,看看它是如何处理的


Hello shiro+springmvc实例--shiro加强篇_第23张图片
image.png

创建一个是定义的sessionManager类CustomSessionManager,并重写retrieveSession方法


public class CustomSessionManager extends DefaultWebSessionManager {
    @Override //sessionKey中存储的是request对象
    protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
        Serializable sessionId = getSessionId(sessionKey);

        //先冲request中去session,取不到我们在到redis中取,然后在设置到request中
        ServletRequest request = null;
        if (sessionKey instanceof  WebSessionKey){
            request = ((WebSessionKey) sessionKey).getServletRequest();
        }

        if (request != null && sessionId != null){
            Session session = (Session) request.getAttribute(sessionId.toString());
            if (session !=null){
                return session;
            }
        }

        Session session = super.retrieveSession(sessionKey);
        if (request != null && sessionId !=null){
            request.setAttribute(sessionId.toString(),session);

        }

        return session;
        
    }
}

在spring中创建sessionManager对象修改为我们自己的

Hello shiro+springmvc实例--shiro加强篇_第24张图片
用上自己的sessionManager
    
    
    
        
        

    

重写部署运行web,并访问,查看日志

Hello shiro+springmvc实例--shiro加强篇_第25张图片
用自己的sessionManager后就没有重复打印

Shiro缓冲管理(CacheManager接口、Cache)

目标:用来缓冲角色数据和权限数据,这样在每次授权是,就不需要每次都到数据库去查询

RedisCache实现Cache接口
/*K key;V :value*/
public class RedisCache implements Cache {
    @Resource /*注入jedisUtil*/
    private JedisUtil jedisUtil;
    private final String CACHE_PRIFIX = "younghare:";

    private byte[] getKey(K k){
      if (k instanceof String){
          return (CACHE_PRIFIX+k).getBytes();
      }
      return SerializationUtils.serialize(k);
    };


    @Override
    public V get(K k) throws CacheException {
        byte[] value = jedisUtil.get(getKey(k));
        if (value != null){
            return (V) SerializationUtils.deserialize(value);//反序列化
        }


        return null;
    }

    @Override
    public V put(K k, V v) throws CacheException {
        byte[] key = getKey(k);
        byte[] value = SerializationUtils.serialize(v);
        jedisUtil.set(key,value);
        jedisUtil.expire(key,10*60); //十分钟

        return v;
    }

    @Override
    public V remove(K k) throws CacheException {
        byte[] key = getKey(k);
        byte[] value = jedisUtil.get(key);
        jedisUtil.del(key);
        if (value != null){
            return (V) SerializationUtils.deserialize(value);
        }

        return null;
    }

    @Override
    public void clear() throws CacheException {
        /*还没有开始实现*/

    }

    @Override
    public int size() {
        /*还没有开始实现*/
        return 0;
    }

    @Override
    public Set keys() {
        /*还没有开始实现*/
        return null;
    }

    @Override
    public Collection values() {
        /*还没有开始实现*/
        return null;
    }
}

修改spring.xml(创建RedisCacheManager对象,在DefaultWebSecurityManager对象中注入RedisCacheManager对象)
    
    
Hello shiro+springmvc实例--shiro加强篇_第26张图片
注入RedisCacheManager对象
重新部署运行web,登录并校验
RedisCacheManager实现CacheManager接口

注意观察从数据库&从redis获取的权限数据

Shiro自动登录

修改spring.xml(创建自动登录对象cookieRememberMeManager,SimpleCookie,配置修改DefaultWebSecurityManager对象的rememberMeManager)
    
    
        

    
    

    
        
        
        
        
    
Hello shiro+springmvc实例--shiro加强篇_第27张图片
修改DefaultWebSecurityManager对象的rememberMeManager
修改UserController中的登录方法subLogin,添加是否记住
Hello shiro+springmvc实例--shiro加强篇_第28张图片
登录时taken设置是否记住
重新部署web
Hello shiro+springmvc实例--shiro加强篇_第29张图片
image.png
Hello shiro+springmvc实例--shiro加强篇_第30张图片
浏览器开发者工具看到
Hello shiro+springmvc实例--shiro加强篇_第31张图片
image.png

你可能感兴趣的:(Hello shiro+springmvc实例--shiro加强篇)