SpringBoot整合shiro实现登录认证,配置redis实现session共享

未完待续。。。

一、Shiro介绍(此章节为搬运)

Shiro 是 Apache 旗下的一个开源安全认证框架,是安全认证方面的一个 Java 类库,实现用户身份认证,权限授权,加密,会话管理(Session)等功能。下面,是 Shiro 中的一些核心概念。

1.1 Realm

领域,处理用户的认证、授权,需要继承 AuthorizingRealm 类,自行重写认证、授权方法。(从数据库获取用户权限信息、密码加密校验等)

1.2 SecurityManager

安全管理器,Shiro 的核心,对用户的认证、授权进行安全管理。

1.3 SessionManager

会话管理器,Shiro 框架自己实现的不依赖Web容器的会话管理,并且可以将Session缓存搭配Redis中,所以非web项目也可以使用Shiro。

1.4 CacheManager

缓存管理。可以将用户的权限信息缓存到内存或者Redis中。

二、整合Shiro

2.1 引入pom依赖

<!-- Shiro核心框架 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.9.1</version>
</dependency>

<!-- Shiro使用Spring框架 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.9.1</version>
</dependency>

2.2 授权与认证

package com.xxx.shiro;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @Author: yiwenli
 * @Description: ShiroRealm shiro与应用安全数据间的"桥梁"或"连接器"
 * @Date: 2022/10/21
 */
@Slf4j
@Component
public class ShiroRealm extends AuthorizingRealm {

    @Value("${shiro.username:admin}")
    private String shiroUsername;
    @Value("${shiro.password:admin}")
    private String shiroPassword;

    /**
     * 授权
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        log.info("执行了授权doGetAuthorizationInfo");
        return null;
    }

    /**
     * 认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {

        log.info("执行了认证doGetAuthenticationInfo");

        // 强转shiro封装成的token
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;

        // TODO 正常应该去数据库中查用户信息

        // 用户名认证
        if (!userToken.getUsername().equals(shiroUsername)) {
            return null;
        }

        // 密码认证
        return new SimpleAuthenticationInfo(shiroUsername, shiroPassword, this.getName());
    }

}

2.3 自定义密码校验

package com.xxx.shiro;

import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;

/**
 * @Author: yiwenli
 * @Description: 自定义密码校验,默认会调用shiro默认的
 * @Date: 2022/10/26
 */
public class MyCredentialsMatcher extends SimpleCredentialsMatcher {

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        UsernamePasswordToken tokenResolve = (UsernamePasswordToken) token;
        String tokenPwd = new String(tokenResolve.getPassword());
        String infoPwd = (String) info.getCredentials();
        // 调用当前类重写的equals方法来对比两个password是否一致,返回对比结果
        return super.equals(tokenPwd, infoPwd);
    }
}

2.4 自定义过滤器配置(解决中文路径400问题)

package com.xxx.shiro;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.InvalidRequestFilter;
import org.apache.shiro.web.filter.mgt.DefaultFilter;
import org.apache.shiro.web.filter.mgt.FilterChainManager;

import javax.servlet.Filter;
import java.util.Map;

/**
 * @Author: yiwenli
 * @Description: 用于解决shiro导致中文路径400非法问题
 * @Date: 2022/10/25
 */
public class MyShiroFilterFactoryBean extends ShiroFilterFactoryBean {

    @Override
    protected FilterChainManager createFilterChainManager() {
        FilterChainManager manager = super.createFilterChainManager();

        Map<String, Filter> filterMap = manager.getFilters();
        Filter invalidRequestFilter = filterMap.get(DefaultFilter.invalidRequest.name());
        if (invalidRequestFilter instanceof InvalidRequestFilter) {
            // 此处是关键,设置false跳过URL携带中文400,servletPath中文校验
            ((InvalidRequestFilter) invalidRequestFilter).setBlockNonAscii(false);
        }
        return manager;
    }
}

2.5 自定义认证拦截器(根据需求确定重定向到页面还是直接返回数据)

package com.xxx.shiro;

import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;

/**
 * @Author: yiwenli
 * @Description: 自定义Authc拦截器:用于解决未登录成功时也会将session信息存到redis中的问题,不再重定向到登录页
 * @Date: 2022/10/31
 */
public class AuthcShiroFilter extends FormAuthenticationFilter {

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception{
        if (isLoginRequest(request, response)) {
            if (isLoginSubmission(request, response)) {
                return executeLogin(request, response);
            } else {
                return true;
            }
        } else {
            // option请求处理
            HttpServletRequest req = (HttpServletRequest) request;
            HttpServletResponse resp = (HttpServletResponse) response;
            if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {
                resp.setStatus(HttpStatus.OK.value());
                return true;
            }

//            this.saveRequestAndRedirectToLogin(request, response);
            // 取消重定向,直接返回结果
            returnTokenInvalid((HttpServletRequest)request, (HttpServletResponse)response);
            return false;
        }
    }

    /**
     * 替代shiro重定向
     */
    private void returnTokenInvalid(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));
        resp.setHeader("Access-Control-Allow-Credentials", "true");
        resp.setContentType("application/json; charset=utf-8");
        resp.setCharacterEncoding("UTF-8");
        Writer out = new BufferedWriter(new OutputStreamWriter(resp.getOutputStream()));
        out.write("无法访问");
        out.flush();
        out.close();
    }
}

2.6 配置类

package com.xxx.shiro;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @Author: yiwenli
 * @Description: Shiro配置类
 * @Date: 2022/10/21
 */
@Configuration
public class ShiroConfig {

    /**
     * 引入定义好的域
     */
    @Bean(name = "shiroRealm")
    public ShiroRealm shiroRealm(){
        // realm(领域)连接数据
        return new ShiroRealm();
    }

    /**
     * 密码校验
     */
    @Bean(name = "myCredentialsMatcher")
    public MyCredentialsMatcher myCredentialsMatcher(){
        return new MyCredentialsMatcher();
    }
    
    /**
     * session配置
     */
    @Bean(name = "sessionManager")
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
        // 会话过期时间
        defaultWebSessionManager.setGlobalSessionTimeout(1000 * 60 * 30);
        defaultWebSessionManager.setSessionValidationInterval(1000 * 60 * 30);
        defaultWebSessionManager.setDeleteInvalidSessions(true);
        defaultWebSessionManager.setSessionValidationSchedulerEnabled(true);
        defaultWebSessionManager.setSessionIdCookieEnabled(true);
        // tomcat的JESSIONID自动生成模块
        defaultWebSessionManager.setSessionIdUrlRewritingEnabled(false);
        return defaultWebSessionManager;
    }

    /**
     * 安全管理器配置
     */
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager webSecurityManager(@Qualifier("shiroRealm") ShiroRealm shiroRealm,
                                                        @Qualifier("myCredentialsMatcher") MyCredentialsMatcher myCredentialsMatcher,
                                                        @Qualifier("sessionManager") DefaultWebSessionManager sessionManager){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 密码校验放入域中
        shiroRealm.setCredentialsMatcher(myCredentialsMatcher);
        // 将域添加到安全管理器中
        securityManager.setRealm(shiroRealm);
        // 设置session管理器
        securityManager.setSessionManager(sessionManager);
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){

        MyShiroFilterFactoryBean factoryBean = new MyShiroFilterFactoryBean();

        // 设置安全管理器
        factoryBean.setSecurityManager(defaultWebSecurityManager);
        // 设置未登录用户跳转页面
        factoryBean.setLoginUrl("/t/html/t-login.html");
        // 设置登录成功返回页面
//        factoryBean.setSuccessUrl("/t/html/t-tingdian-index.html");
        // 设置未授权返回页面
//        factoryBean.setUnauthorizedUrl();

        // 过滤定义
        Map<String, Filter> filtersMap = new LinkedHashMap<>();
        /*// 配置自定义登出 覆盖logout之前默认的LogoutFilter
        LogoutFilter logoutFilter = new LogoutFilter();
        logoutFilter.setRedirectUrl("/t/html/t-login.html");
        filtersMap.put("logout", logoutFilter);*/
        /*// 配置自定义认证拦截器
        filtersMap.put("authc", new AuthcShiroFilter());*/
        factoryBean.setFilters(filtersMap);

        // 过滤链定义,从上向下顺序执行,必须是链式,保证有序
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

        // 设置无需认证就可以访问的页面
        filterChainDefinitionMap.put("/t/html/t-login.html", "anon");
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/getPublicKeyStr", "anon");
        filterChainDefinitionMap.put("/**/*.js", "anon");
        filterChainDefinitionMap.put("/**/*.json", "anon");
        filterChainDefinitionMap.put("/**/*.css", "anon");
        filterChainDefinitionMap.put("/**/*.woff", "anon");
        filterChainDefinitionMap.put("/**/*.ttf", "anon");
        filterChainDefinitionMap.put("/**/*.png", "anon");
        filterChainDefinitionMap.put("/**/*.svg", "anon");

        // 最简单版登录页引用的静态文件
//        filterChainDefinitionMap.put("/t/pic/login-back.png", "anon");
//        filterChainDefinitionMap.put("/t/pic/browserLogo.ico", "anon");

        // 开启shiro内置退出过滤器,完成退出功能(不用自己写退出方法了)
//        filterChainDefinitionMap.put("/logout", "logout");

        // 设置必须认证才能访问的页面
        filterChainDefinitionMap.put("/**", "authc");

        // 设置shiro的内置过滤器,配置访问权限
        factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return factoryBean;
    }

}

2.7 登录相关功能控制器

package com.xxx.tingdian.controller;

import com.ieslab.common.RSAUtil;
import com.ieslab.common.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: yiwenli
 * @Description: 登录Controller
 * @Date: 2022/10/26
 */
@Slf4j
@RestController
public class LoginController {

    /**
     * 获取数据加密公钥
     */
    @GetMapping(value = "/getPublicKeyStr", produces = "application/json;charset=utf-8")
    public String getPublicKeyStr() {
        return Result.success(RSAUtil.getPublicKeyStr()).toJsonString();
    }

    /**
     * 用户登录
     */
    @PostMapping(value = "/login", produces = "application/json;charset=utf-8")
    public String login(String username, String password) {
        if (StringUtils.isBlank(username)) {
            return Result.error("用户名不能为空").toJsonString();
        }
        if (StringUtils.isBlank(password)) {
            return Result.error("密码不能为空").toJsonString();
        }
        Subject subject = SecurityUtils.getSubject();
        try {
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, RSAUtil.decrypt(password));
            subject.login(usernamePasswordToken);
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            return Result.error("用户名错误").toJsonString();
        } catch (IncorrectCredentialsException e) {
            e.printStackTrace();
            return Result.error("密码错误").toJsonString();
        }
        return Result.success().toJsonString();
    }

    /**
     * 用户退出登录
     */
    @GetMapping(value = "/logout", produces = "application/json;charset=utf-8")
    public String logout() {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return Result.success().toJsonString();
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值