springboot集成shiro,使用token登陆,使用redis缓存

1.准备用户数据,这里我将数据写死存储;

public class Constant implements Serializable {
    public static final String USERNAME = "gaoyang";
    public static final String PASSWORD = "123456";
    public static final String ROLE1 = "admin";
    public static final String ROLE2 = "guest";

    public static  Map map;
    public static Map get(){
        if(map==null){
            map = new HashedMap();
        }
        return map;
    }
}

2.配置redis,这里value序列化使用的是jdk自己的,使用json会报错

spring:
  redis:
    host: 192.168.32.128
    port: 6382
    password: 123456
    jedis:
      pool:
        max-wait: 1
        max-idle: 10
        max-active: 8
        min-idle: 5
    timeout: 5000
@EnableCaching
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        JdkSerializationRedisSerializer serializer = new JdkSerializationRedisSerializer();
        template.setValueSerializer(serializer);
        template.setKeySerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }

    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
        stringRedisTemplate.setConnectionFactory(factory);
        return stringRedisTemplate;
    }
}

3.重写sessionid接受方式,这里我们接收前台传过来的token

public class SessionConfig extends DefaultWebSessionManager {
    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        String id = WebUtils.toHttp(request).getHeader("token");
        
        if (!StringUtils.isEmpty(id)) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "Stateless request");
            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);
        }
    }
}

4.重写生成,读取,变更token信息的方法,使用redis保存

@Component
public class SessionDaoConfig extends EnterpriseCacheSessionDAO {

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Override
    protected Serializable doCreate(Session session) {
        Serializable sessionId = this.generateSessionId(session);
        SimpleSession session1 = (SimpleSession) session;
        session1.setId(sessionId);
        return sessionId;
    }

    @Override
    protected Session doReadSession(Serializable sessionId) {
        Session o = (Session) redisTemplate.opsForValue().get(sessionId);
        return o;
    }

    @Override
    protected void doUpdate(Session session) {
        if (session instanceof ValidatingSession) {
            System.out.println(session.getTimeout());
            System.out.println(session.getId());
            if (((ValidatingSession) session).isValid()) {
                redisTemplate.opsForValue().set(session.getId(), session);
            } else {
                redisTemplate.delete(session.getId());
            }
        } else {
            redisTemplate.opsForValue().set(session.getId(), session);
        }
    }

    @Override
    protected void doDelete(Session session) {
        redisTemplate.delete(session.getId());
    }
}

5.自定义realm验证方法

@Component("myRealm")
public class MyRealm extends AuthorizingRealm {
    @Override
    public String getName() {
        return "myRealm";
    }

    //权限认证,通过名字读取角色死数据
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String username = (String) principalCollection.getPrimaryPrincipal();
        System.out.println(username);
        SimpleAuthorizationInfo sim = new SimpleAuthorizationInfo();
        if (username.equals("gaoyang")) {
            sim.addRole(Constant.ROLE1);
        } else {
            sim.addRole(Constant.ROLE2);
        }
        return sim;
    }

    //身份验证,这里密码写死
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String username = (String) authenticationToken.getPrincipal();
        if (username == null) {
            return null;
        }
        SimpleAuthenticationInfo sim = new SimpleAuthenticationInfo(username, Constant.PASSWORD, this.getName());
        return sim;
    }
    /*@Override
    public CredentialsMatcher getCredentialsMatcher() {
        HashedCredentialsMatcher hash = new HashedCredentialsMatcher();
        hash.setHashAlgorithmName("md5");
        hash.setHashIterations(2);
        return hash;
    }*/
}

6.设置shiro权限控制中心

@Configuration
public class ShiroConfig   {
    @Autowired
    private MyRealm myRealm;

    @Bean("shirFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager) {
        ShiroFilterFactoryBean shiro = new ShiroFilterFactoryBean();
        shiro.setLoginUrl("/unlogin");  //没有登陆的json返回
        shiro.setUnauthorizedUrl("/no");    //没有权限的json返回
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        // 配置不会被拦截的链接 顺序判断
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/ajaxLogin", "anon");
        filterChainDefinitionMap.put("/map", "anon");
        filterChainDefinitionMap.put("/login", "anon");
        //注意,如果roles[admin,guest]是用户需要同时包含两者角色才可以访问,是且的关系;
        //如果想改为或的关系,请继承AuthorizationFilter并加入过滤连,perm资源也是一样,需要继承PermissionsAuthorizationFilter加入过滤链;
        filterChainDefinitionMap.put("/test", "authc,roles[admin]");
        filterChainDefinitionMap.put("/**", "authc");
        shiro.setFilterChainDefinitionMap(filterChainDefinitionMap);
        shiro.setSecurityManager(securityManager);
        return shiro;
    }


    @Bean("securityManager")
    public SecurityManager securityManager(SessionDaoConfig sessionDaoConfig) {
        DefaultWebSecurityManager def = new DefaultWebSecurityManager();
        def.setRealm(myRealm);
        // 自定义session管理 使用redis
        SessionConfig sessionConfig = new SessionConfig();
        sessionConfig.setSessionDAO(sessionDaoConfig);
        //sessionConfig.setSessionDAO(new SessionDaoConfig());
        def.setSessionManager(sessionConfig);
        // 自定义缓存实现 使用redis
        //def.setCacheManager();
        return def;
    }
}

7.controller层的登陆,退出等

@RestController
public class IndexController {

    @Autowired
    private RedisTemplate<Object,Object>redisTemplate;

    @CrossOrigin
    @GetMapping("/login")
    public Object login(String username, String password) {
        Map<String, Object> map = new HashMap<>();
        map.put("code", 200);
        map.put("msg", "登录成功");
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        Subject subject = SecurityUtils.getSubject();
        map.put("token", subject.getSession().getId());
        try {
            subject.login(token);
            Session session = subject.getSession();
            //这里设置session过期时间
            session.setTimeout(25000);
            session.setAttribute("currentuser", username);
        } catch (Exception e) {
            map.put("code", 204);
            map.put("msg", "登录失败");
            return map;
        }
        return map;
    }

    @GetMapping("/unlogin")
    public Object unlogin() {
        Map<String, Object> map = new HashMap<>();
        map.put("code", 204);
        map.put("msg", "未登录");
        return map;
    }

    @GetMapping("/test")
    public Object test() {
        Map<String, Object> map = new HashMap<>();
        Subject subject = SecurityUtils.getSubject();
        Object currentuser = subject.getSession().getAttribute("currentuser");
        map.put("code", 200);
        map.put("msg", "查询成功");
        map.put("data", currentuser);
        return map;
    }

    @GetMapping("/logout")
    public Object logout() {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        Map<String, Object> map = new HashMap<>();
        map.put("code", 200);
        map.put("msg", "退出成功");
        return map;
    }

8.自定义的过滤替代roles和perms

public class PermConfig extends PermissionsAuthorizationFilter {
    @Override
    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
        Subject subject = this.getSubject(request, response);
        String[] rolesArray = (String[]) mappedValue;
        if (rolesArray == null || rolesArray.length == 0) {
            //no roles specified, so nothing to check - allow access.
            return true;
        }
        for (int i = 0; i < rolesArray.length; i++) {
            if (subject.isPermitted(rolesArray[i])) {
                return true;
            }
        }
        return false;
    }
}
public class RoleConfig extends AuthorizationFilter {
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
        Subject subject = this.getSubject(servletRequest, servletResponse);
        String[] rolesArray = (String[]) o;

        if (rolesArray == null || rolesArray.length == 0) {
            //no roles specified, so nothing to check - allow access.
            return true;
        }
        for (int i = 0; i < rolesArray.length; i++) {
            if (subject.hasRole(rolesArray[i])) {
                return true;
            }
        }
        return false;
    }
}

不过作为前后端分离项目,用户的信息及过期权限等信息依然是靠后端存储,以上依然涉及session,只不过是将产生的jsessionid当作token使用,使用redis存储而已.可以考虑使用jwt,彻底是后端无状态化;

评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值