Apache Shiro session cache + + Remember me (c)

1, session management SessionDao and SessionManager

1) Mounting the Redis
2) dependent

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.8.0</version>
        </dependency>

3) Configure redis bean connection pool:

    @Bean
    public JedisPoolConfig getJedisPoolConfig() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        return jedisPoolConfig;
    }

    @Bean
    public JedisPool getJedisPool() {
        JedisPool jedisPool = new JedisPool(getJedisPoolConfig(), "129.204.58.30", 6379);
        return jedisPool;
    }

4) write redis tools:

package com.example.demo_mg.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.Set;

@Component
public class RedisUtil {
    @Autowired
    private JedisPool jedisPool;

    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();
        }
    }

    public void expire(byte[] key, int i) {
        Jedis jedis = getResource();
        try {
            jedis.expire(key, i);
        }  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();
        }
    }

    //获取指定前缀所有Key
    public Set<byte[]> keys(String prefix) {
        Jedis jedis = getResource();
        try {
            return jedis.keys((prefix + "*").getBytes());
        }  finally {
            jedis.close();
        }
    }
}

5) write SessionDao inheritance AbstractSessionDAO:

package com.example.demo_mg.session;

import com.example.demo_mg.util.RedisUtil;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.SerializationUtils;

import javax.annotation.Resource;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

@Component
public class RedisSessionDao extends AbstractSessionDAO {
    @Resource
    private RedisUtil redisUtil;

    private final String SHIRO_SESSION_PREFIX = "shiro_session:";

    private byte[] getKey(String key) {
        return (SHIRO_SESSION_PREFIX + key).getBytes();
    }

    private void saveSession(Session session) {
        if(session != null && session.getId() !=null) {
            byte[] key = getKey(session.getId().toString());
            byte[] value = SerializationUtils.serialize(session);
            redisUtil.set(key, value);
            redisUtil.expire(key, 600);
        }
    }

    @Override
    protected Serializable doCreate(Session session) {
        Serializable sessionId = generateSessionId(session);
        assignSessionId(session, sessionId); //需要捆绑,否则登录会抛异常
        saveSession(session);
        return sessionId;
    }

//    serializable是sessionId
    @Override
    protected Session doReadSession(Serializable serializable) {
        System.out.println("read session");
        if(serializable == null) {
            return null;
        }
        byte[] key = getKey(serializable.toString());
        byte[] value = redisUtil.get(key);
        return (Session) SerializationUtils.deserialize(value);
    }

    @Override
    public void update(Session session) throws UnknownSessionException {
        saveSession(session);
    }

    @Override
    public void delete(Session session) {
        if(session == null || session.getId() == null) {
            return;
        }
        byte[] key = getKey(session.getId().toString());
        redisUtil.del(key);
    }

    @Override
    public Collection<Session> getActiveSessions() {
        Set<byte[]> keys = redisUtil.keys(SHIRO_SESSION_PREFIX);
        Set<Session> sessions = new HashSet<>();
        if(CollectionUtils.isEmpty(keys)) {
            return sessions;
        }
        for (byte[] key : keys) {
            Session session = (Session) SerializationUtils.deserialize(redisUtil.get(key));
            sessions.add(session);
        }
        return sessions;
    }
}

6) modify the configuration based on bean (II) on:

新配置两个bean
    @Bean
    public RedisSessionDao getRedisSessionDao() {
        RedisSessionDao redisSessionDao = new RedisSessionDao();
        return redisSessionDao;
    }

    @Bean
    public DefaultWebSessionManager getDefaultWebSessionManager(RedisSessionDao redisSessionDao) {
        DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
        defaultWebSessionManager.setSessionDAO(redisSessionDao);
        return defaultWebSessionManager;
    }

修改一个bean(添加会话管理)
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(TestRealm testRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(testRealm);

        //会话管理
        securityManager.setSessionManager(getDefaultWebSessionManager(getRedisSessionDao()));

        return  securityManager;
    }

7) login verification, simply login, spooling five times read session, redis client to perform keys *, found "shiro_session: 19b704b8-3324-4f88-92c0-c0effb5243a6".
In the method DefaultSessionManager retrieveSession source, call the Session s = this.retrieveSessionFromDataSource (sessionId); and the method call this.sessionDAO.readSession (sessionId); readSession AbstractSessionDao the method then adjust code Session s = this.doReadSession (sessionId) ; call RedisSessionDao own implementation of doReadSession method. All, to reduce the number to Redis reading session, the need to rewrite their own retrieveSession method, the transformation of Session s = this.retrieveSessionFromDataSource (sessionId) ;.

SessionManager define a class that inherits DefaultWebSessionManager class overrides retrieveSession method:

package com.example.demo_mg.session;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.session.mgt.WebSessionKey;

import javax.servlet.ServletRequest;
import java.io.Serializable;

/**
 * sessionKey对象里有request对象,可以第一次查询以后把session放在request对象里,就不需要频繁查询redis。
 */
public class RedisSessionManager extends DefaultWebSessionManager {
    @Override
    protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
        Serializable sessionId = getSessionId(sessionKey);
        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;
    }
}

Modify the configuration bean:

    @Bean
    public DefaultWebSessionManager getDefaultWebSessionManager(RedisSessionDao redisSessionDao) {
//        DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
        DefaultWebSessionManager defaultWebSessionManager = new RedisSessionManager(); //改用自己定义的SessionManager
        defaultWebSessionManager.setSessionDAO(redisSessionDao);
        return defaultWebSessionManager;
    }

2, cache management, Cache and CacheManager:

Cache roles, permissions information, requires authorization, certification can not, you can use redis, echache or map to implement caching CacheManager

1) Custom Cache:

package com.example.demo_mg.cache;

import com.example.demo_mg.util.RedisUtil;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.springframework.stereotype.Component;
import org.springframework.util.SerializationUtils;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.Set;

@Component
public class RedisCache<K, V> implements Cache<K, V> {
    @Resource
    private RedisUtil redisUtil;

    private final String CACHE_PREFIX = "shiro_cache:";

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

    //该方法还可以用Map在本地做二级缓存
    @Override
    public V get(K k) throws CacheException {
        System.out.println("缓存读取授权信息");
        byte[] value = redisUtil.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);
        redisUtil.set(key, value);
        redisUtil.expire(key, 600);
        return v;
    }

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

    @Override
    public void clear() throws CacheException {
        //只清空缓存用的,慎重,小心将Redis的所有缓存清空
    }

    @Override
    public int size() {
        return 0;
    }

    @Override
    public Set<K> keys() {
        return null;
    }

    @Override
    public Collection<V> values() {
        return null;
    }
}

2) Custom CacheManager:

package com.example.demo_mg.cache;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;

import javax.annotation.Resource;


public class RedisCacheManager implements CacheManager {
    @Resource
    private RedisCache redisCache;

    //这里的String s可以用来创建一个concurrentHashMap缓存cache名称和cache实例,s就是cache名称,这里只有一个redisCache对象就不用map了。
    @Override
    public <K, V> Cache<K, V> getCache(String s) throws CacheException {
        return redisCache;
    }
}

3) Configure categories:

新增bean
    @Bean
    public RedisCacheManager getRedisCacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        return redisCacheManager;
    }

修改bean(添加缓存管理)
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(TestRealm testRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(testRealm);

        //会话管理
        securityManager.setSessionManager(getDefaultWebSessionManager(getRedisSessionDao()));

        //缓存管理
        securityManager.setCacheManager(getRedisCacheManager());

        return  securityManager;
    }

4) Add tag (plus two System.out print only the beginning and end obtain authorization information from the database) in the Realm:

package com.example.demo_mg.realm;

import org.apache.commons.collections.map.HashedMap;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

import java.util.*;

public class TestRealm extends AuthorizingRealm {
    //模拟users、user_roles、roles_permissions三张表的查询,实际应用需要查询数据库或缓存
    Map<String, String> users = new HashMap<>();
    Map<String, Set<String>> user_roles = new HashedMap();
    Map<String, Set<String>> roles_permissions = new HashedMap();
//    String salt = UUID.randomUUID().toString().replaceAll("-","");

    {
        //不加盐(与认证对应)
        users.put("wzs", new Md5Hash("123456",null, 2).toString());
        //加盐
//        users.put("wzs", new Md5Hash("123456",salt, 2).toString());
        user_roles.put("wzs", new HashSet<>(Arrays.asList("admin", "test")));
        roles_permissions.put("admin", new HashSet<>(Arrays.asList("user:delete", "user:update")));
        super.setName("TestRealm"); //设置Realm名称,可选
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //从认证信息获取用户名
        String username = (String)principalCollection.getPrimaryPrincipal();
        //从数据库或缓存中获取角色、权限数据
        System.out.println("数据库获取认证信息start:");
        Set<String> roles = user_roles.get(username);
        Set<String> permissions = new HashSet<>();
        for (String role : roles) {
            Set<String> set;
            if((set = roles_permissions.get(role)) != null) {
                permissions.addAll(set);
            }
        }
        System.out.println("数据库获取认证信息end.");
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setRoles(roles);
        simpleAuthorizationInfo.setStringPermissions(permissions);
        return simpleAuthorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //从主题传过来的认证信息中,获得用户名
        String username = (String)authenticationToken.getPrincipal();
        //通过用户名从数据库中获取凭证
        String password = users.get(username);
        if(password != null) {
            //不加盐
//            return new SimpleAuthenticationInfo(username, password, super.getName());
            //加盐
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, password, super.getName());
//            simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(salt));
            return simpleAuthenticationInfo;
        }
        return null;
    }
}

5) Test results:
first visit backstage with @RequireRole method annotated print:
the Read the session
cache reads authorization information
database to obtain authentication information start:
database to obtain authentication information end.

Second visit Spooler:
the Read the session
cache reads authorization information

The first explanation from the cache did not get to go get a database and cached, later acquired directly from the cache.

Redis clients perform keys *, we have the following key:

 "\xac\xed\x00\x05sr\x002org.apache.shiro.subject.SimplePrincipalCollection\xa                                     8\x7fX%\xc6\xa3\bJ\x03\x00\x01L\x00\x0frealmPrincipalst\x00\x0fLjava/util/Map;xp                                     sr\x00\x17java.util.LinkedHashMap4\xc0N\\\x10l\xc0\xfb\x02\x00\x01Z\x00\x0bacces                                     sOrderxr\x00\x11java.util.HashMap\x05\a\xda\xc1\xc3\x16`\xd1\x03\x00\x02F\x00\nl                                     oadFactorI\x00\tthresholdxp?@\x00\x00\x00\x00\x00\x0cw\b\x00\x00\x00\x10\x00\x00                                     \x00\x01t\x00\tTestRealmsr\x00\x17java.util.LinkedHashSet\xd8l\xd7Z\x95\xdd*\x1e                                     \x02\x00\x00xr\x00\x11java.util.HashSet\xbaD\x85\x95\x96\xb8\xb74\x03\x00\x00xpw                                     \x0c\x00\x00\x00\x02?@\x00\x00\x00\x00\x00\x01t\x00\x03wzsxx\x00w\x01\x01q\x00~\                                     x00\x05x"

3, RememberMe, I remember, automatic login, add the boolean remeberMe property in the User object, the login form to add "Remember Me," the CheckBox:
1) Log transformation method

    @RequestMapping(value = "/login",method = RequestMethod.GET)
    public String loginUser(String username, String password, boolean rememberMe) {
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
        Subject subject = SecurityUtils.getSubject();
        try {
            usernamePasswordToken.setRememberMe(rememberMe);  //记住我功能
            subject.login(usernamePasswordToken);   //完成登录
            //更新用户登录时间,也可以在ShiroRealm里面做
            return "index";
        } catch(Exception e) {
            return "login";//返回登录页面
        }
    }

2) Configuration bean

添加2个bean
    @Bean
    public SimpleCookie getSimpleCookie() {
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe"); //生成cookie名称
        simpleCookie.setMaxAge(600); //生成cookie过期时间,单位秒
        return simpleCookie;
    }

    @Bean
    public CookieRememberMeManager getCookieRememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(getSimpleCookie());
        return cookieRememberMeManager;
    }

修改一个bean(添加RememberMe)
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(TestRealm testRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(testRealm);

        //会话管理
        securityManager.setSessionManager(getDefaultWebSessionManager(getRedisSessionDao()));

        //缓存管理
        securityManager.setCacheManager(getRedisCacheManager());

        //RememberMe
        securityManager.setRememberMeManager(getCookieRememberMeManager());

        return  securityManager;
    }

3) Verification: After logging under Application Browser F12's, under Cookies, http: // localhost: 8080 There is a called a cookie rememberMe of. And the back page after the restart did not adjust to the login page description Remember me into effect.

Guess you like

Origin www.cnblogs.com/kibana/p/11111529.html