springboot+shiro+redis前后端分离实现认证(一)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 Tair(兼容Redis),内存型 2GB
简介: springboot+shiro+redis前后端分离实现认证(一)

 springboot+shiro+redis前后端分离实现认证(一)

一、shiro架构图与基本知识

四大功能

(1)认证

(2)授权

(3)加密

(4)会话管理

1.1 Subject

Subject 即主题,外部应用与subject进行交互,subject记录了当前操作用户,将用户当前的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。

Subject在shiro中是一个接口,接口中提供了许多认证授权的相关方法,外部程序通过subject进行认证授权,而subject通过subject通过SecurityManager进行认证授权。

1.2 SecurityManager

SecurityManager即安全管理器,对于所有的subject进行安全管理,它是shiro的核心,负责对所有的subject进行管理,通过SecurityManager可以完成Subject的认证授权等。SecurityManager通过三部分进行完成:

一、Authenticator(进行认证);二、Authorizer(进行授权);三、SessionManager进行会话管理。

1.2.1 Authenticator

Authenticator 即认证器,对用户身份进行认证。Authenticator是一个借口shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以实现大多数需求,也可以自定义拦截器。

1.2.2 Authorizer

Authorizer 即授权器。用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。

1.2.3 SessionManager

sessionManager 即会话管理,shiro框架定义了一套会话管理,他不依赖web容器的session,所有shiro可以适用于非web应用上,也可以将分布式应用的会话集中在一点管理,该特性可以使他实现单点登录。

1.2.4 SessionManager中的SessionDAO

essionDAO即会话dao,是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会话存储到数据库。

1.3 Realm

Realm 即领域,相当于DataSource数据源,securityManager进行安全认证需要通过Realm获取用户安全数据。比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。

注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。

1.4 CacheManager

CacheManager即缓存管理,将用户权限数据存储在缓存,这样可以提高性能。

1.5 Cryptography

Cryptography 即密码管理,shiro提供了一套加密、解密的组件,方便开发。比如提供常用的散列、加/解密等功能。

二、代码前期准备:

2.1 前期准备(点击打开页面)

(1)Linux下Redis简介、安装、设置、启动

(2)Linux系统中Mysql5.7建立远程连接

(3)Linux防火墙知识简介+命令规则

(4)解决项目中确实tools.jar的问题

(5)Linux centos 6.5 - Mysql 安装 、卸载、修改密码、忘记密码 并异常处理

2.2 建立springboot项目(没有教程网上一大堆)

2.3 application.properties配置

因为装了Mysql和Redis所以配置了两个数据源进行连接。

#-------------数据源一(画了黄线也不要紧,配置类里面配置好了就行)--Mysql-------------------
spring.datasource.primary.url=jdbc:mysql://192.168.1.234:3306/new_erp?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.primary.username=mcb
spring.datasource.primary.password=123456
spring.datasource.primary.driver-class-name=com.mysql.jdbc.Driver
 
#-------------数据源二--Redis---------------------------------------------------
spring.datasource.redis.host=192.168.1.234
spring.datasource.redis.port=6379
spring.datasource.redis.timeout=360000
spring.datasource.redis.password=123456
 
//驼峰命名法修正
mybatis.configuration.map-underscore-to-camel-case=true 
 
#---------------日志配置信息-------------------------------------------------------
 
logging.level.root=info

2.4 配置多个数据源的类

/**
 * 
 */
package com.yuyi.config;
 
import javax.sql.DataSource;
 
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
 
/**
 * @author mcb 
 *
 * 2018年9月3日 下午2:08:11 
 */
@Configuration
public class DataSourceConfig {
  @Bean(name = "primaryDataSource")
  @Primary
  @Qualifier("primaryDataSource")
  @ConfigurationProperties(prefix = "spring.datasource.primary")
  public DataSource primaryDataSource() {
    return DataSourceBuilder.create().build();
  }
  
  
  @Bean(name = "secondaryDataSource")
  @Qualifier("secondaryDataSource")
  @ConfigurationProperties(prefix = "spring.datasource.redis")
  public DataSource secondaryDataSource(){
    return DataSourceBuilder.create().build();
  }
  
  @Bean(name = "primaryJdbcTemplate")
  public JdbcTemplate primaryJdbcTemplate(
      @Qualifier("primaryDataSource") DataSource dataSource) {
    return new JdbcTemplate(dataSource);
  }
 
  @Bean(name = "secondaryJdbcTemplate")
  public JdbcTemplate secondaryJdbcTemplate(
      @Qualifier("secondaryDataSource") DataSource dataSource) {
    return new JdbcTemplate(dataSource);
  }
}

2.5 pom文件配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.10.RELEASE</version>
    <relativePath /> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.yuyi</groupId>
  <artifactId>erp_new</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  <name>erp_new</name>
  <description>Demo project for Spring Boot</description>
 
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
  </properties>
 
  <dependencies>
    <!-- shiro -->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring</artifactId>
      <version>1.4.0</version>
    </dependency>
    
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>1.2.3</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-web</artifactId>
      <version>1.2.3</version>
    </dependency>
  <!-- alibaba-fastjson-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.47</version>
    </dependency>
    <!-- springboot-redis -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-redis</artifactId>
      <version>1.3.2.RELEASE</version>
    </dependency>
    <!-- shiro-redis -->
     <dependency>
      <groupId>org.crazycake</groupId>
      <artifactId>shiro-redis</artifactId>
      <version>3.1.0</version>
    </dependency>  
    <dependency>
      <groupId>com.sun</groupId>
      <artifactId>tools</artifactId>
      <version>1.8.0</version>
    </dependency> 
 
    <!-- springboot必备jar包 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web-services</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <exclusions>
        <!-- 打war包时移除tomcat -->
        <exclusion>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
      </exclusions>
      <version>1.3.2</version>
    </dependency>
 
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
<!-- For log4j -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.7</version>
    </dependency>
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>
 
  </dependencies>
 
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
 
</project>

如果遇到tools.jar问题,上面有个必备知识(4)可以解决。

2.6 数据库(简洁版) 表user

2.7 model 代码

/**
 * 
 */
package com.yuyi.model;
 
import java.io.Serializable;
 
/**
 * @author mcb 
 *
 * 2018年12月10日 下午4:45:47 
 */
public class User implements Serializable {
  
  private static final long serialVersionUID = 7416373978493379166L;
  
  private int id;
  private String username;
  private String password;
  private String salt;
  
  public String getSalt() {
    return salt;
  }
  public void setSalt(String salt) {
    this.salt = salt;
  }
  public int getId() {
    return id;
  }
  public void setId(int id) {
    this.id = id;
  }
  public String getUsername() {
    return username;
  }
  public void setUsername(String username) {
    this.username = username;
  }
  public String getPassword() {
    return password;
  }
  public void setPassword(String password) {
    this.password = password;
  }
  @Override
  public String toString() {
    return "User [id=" + id + ", username=" + username + ", password=" + password + ", salt=" + salt + "]";
  }
 
 
}

2.8 UserDAO

/**
 * 
 */
package com.yuyi.mcb.dao;
 
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
 
import com.yuyi.model.User;
 
/**
 * @author mcb 
 *
 * 2018年12月10日 下午3:58:54 
 */
@Mapper
public interface UserDAO {
  
  @Select("select password from user where username=#{username}")
  String findPass(String username);
  
  @Select("select * from user where username=#{username}")
  User getUserByUsername(String username);
  
 
}

2.9 UserService

/**
 * 
 */
package com.yuyi.mcb.service;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import com.yuyi.mcb.dao.UserDAO;
import com.yuyi.model.User;
 
/**
 * @author mcb
 *
 *         2018年12月10日 下午4:15:20
 */
@Service("userService")
public class UserService {
 
  @Autowired
  private UserDAO dao;
 
  public String findPass(String username) {
 
    // 之后写业务逻辑
    return dao.findPass(username);
  }
 
  public User getUserByUsername(String username) {
    // 之后写业务逻辑
    return dao.getUserByUsername(username);
 
  }
 
}

2.10 拦截器类(个人补充代码,不看也罢

(1)对后台请求进行统一拦截

package com.yuyi.config;
 
import java.util.Map;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
 
 
/**
 * @author Administrator
 *  拦截器类
 */
public class BootInterceptor implements HandlerInterceptor {
   
  private static final Logger logger = LoggerFactory.getLogger(BootInterceptor.class);
  
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        /**
         * 对来自后台的请求统一进行日志处理
         */
        String url = request.getRequestURL().toString();
        String method = request.getMethod();
        String uri = request.getRequestURI();
//        String queryString = request.getQueryString();
        Map<String, String[]>map=request.getParameterMap();   
        System.out.println("---------------------------------------------------------------------------------------------------");
        map.forEach((k,v) ->{
          logger.info("请求参数-- "+k+": "+v[0]);
        });
        logger.info("url--"+url);
        logger.info("method--"+method);
        logger.info("uri--"+uri);
//        logger.info("请求参数-- "+queryString);
        System.out.println("---------------------------------------------------------------------------------------------------");
        return true;
    }
 
    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
      
    }
 
    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
    
    }
}

(2)编码配置

package com.yuyi.config;
 
import java.io.IOException;
 
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
 
/**
 * 编码 过滤器
 */
@Component
public class EncodeFilter implements Filter{
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}
 
    /**
     * 设置编码为UTF-8
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
 
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        
        System.out.println("EncodeFilter");
        
        //过滤结束,继续执行    没有这一行,程序不会继续向下执行
        chain.doFilter(req, res); 
        
    }
     
    @Override
    public void destroy() {}
 
}

(3)拦截配置

/**
 * 
 */
package com.yuyi.config;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
 
/**
 * @author mcb
 * 2018年6月27日 下午4:13:08
 *         
 */
@Configuration
public class WebMvcConfigurer extends WebMvcConfigurerAdapter{
  //增加拦截器
  
  @Bean
    public WebMvcConfigurer getInterfaceAuthCheckInterceptor() {
        return new WebMvcConfigurer();
    }
 
  
  
  //等部署完了,将这个方法注释一下看看。
    public void addInterceptors(InterceptorRegistry registry){
        registry.addInterceptor(new BootInterceptor())    //指定拦截器类
                .addPathPatterns("/**");        //指定该类拦截的url
    }
}

三、认证流程

3.1 shiro的config信息,shiro中的session结合redis

/**
 * 
 */
package com.yuyi.config.shiro;
 
import java.util.LinkedHashMap;
import java.util.Map;
 
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
import com.yuyi.mcb.shiro.MyShiroRealmService;
 
/**
 * @author mcb 
 *
 * 2018年12月10日 下午5:03:49 
 */
@Configuration
public class MyShiroConfig {
  
  
  /**
   * 一、在认证中:
   *    1.1,将加密算法定义好后扔到 MyShiroRealm中 也就是自己定义的realm中
   *    1.2,将MyShiroRealm定义后扔到SecurityManager中。
   *    1.3,后期用到session什么的,都被SecurityManager管理
   * 
   * @return
   */
  
  
  /**
   * 二、配置session(用Redis存储)
   *    2.1 需要配置session,就需要将sessionManager配置在SecurityManager中。
   *    2.2 sessionManager需要交给Redis来管理,所以定义了RedisSessionDAO
   *    2.3 RedisSessionDAO中需要配置Redis的信息,所以定义RedisManager
   * 
   * @return
   */
  
  
  @Value("${spring.datasource.redis.host}")
  private String host;
  @Value("${spring.datasource.redis.port}")
  private int port;
  @Value("${spring.datasource.redis.timeout}")
  private int timeout;
  @Value("${spring.datasource.redis.password}")
  private String password;
  
  
  
  
//-------------------------认证---------------------------
  @Bean
  public SecurityManager securityManager() {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(myShiroRealm());
    securityManager.setSessionManager(sessionManager());
//     // 自定义缓存实现 使用redis
//        securityManager.setCacheManager(cacheManager());  
    return securityManager;
  }
 
  @Bean
  public MyShiroRealmService myShiroRealm() {
    MyShiroRealmService myShiroRealm = new MyShiroRealmService();
    myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
    return myShiroRealm;
  } 
  
  @Bean("hashedCredentialsMatcher")
  public HashedCredentialsMatcher hashedCredentialsMatcher() {
    HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
    // 指定加密方式为MD5
    credentialsMatcher.setHashAlgorithmName("MD5");
    // 加密次数
    credentialsMatcher.setHashIterations(2);
    credentialsMatcher.setStoredCredentialsHexEncoded(true);
    return credentialsMatcher;
  }
  
  
//-------------------------redis-session----------------------
  
  //自定义sessionManager
   @Bean
   public SessionManager sessionManager() {
   MySessionManager mySessionManager = new MySessionManager();
   mySessionManager.setSessionDAO(redisSessionDAO());
   return mySessionManager;
   }
  
  
  
  /**
   * RedisSessionDAO shiro sessionDao层的实现 通过redis
   * <p>
   * 使用的是shiro-redis开源插件
   */
  @Bean
  public RedisSessionDAO redisSessionDAO() {
    RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
    redisSessionDAO.setRedisManager(redisManager());
    redisSessionDAO.setExpire(1800);
    return redisSessionDAO;
  }
  
  /**
   * 配置shiro redisManager
   * <p>
   * 使用的是shiro-redis开源插件
   *
   * @return
   */
  public RedisManager redisManager() {
    RedisManager redisManager = new RedisManager();
    redisManager.setHost(host);
    redisManager.setPort(port);
    redisManager.setTimeout(timeout);
    redisManager.setPassword(password);
    return redisManager;
  }
  
 
  
}

3.2 获取sessionId

如果请求头中有 Authorization 则其值为sessionId, 否则按默认规则从cookie取sessionId

/**
 * 
 */
package com.yuyi.config.shiro;
 
import java.io.Serializable;
 
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
 
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
 
 
/**
 * @author mcb
 *
 *         2018年8月30日 下午5:03:17 自定义sessionId获取
 */
public class MySessionManager extends DefaultWebSessionManager {
 
  
  private static final Logger log = LoggerFactory.getLogger(MySessionManager.class);
 
  private static final String AUTHORIZATION = "Authorization";
 
  private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
 
  public MySessionManager() {
    super();
  }
 
  @Override
  protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
    String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
    // 如果请求头中有 Authorization 则其值为sessionId
    if (!StringUtils.isEmpty(id)) {
      log.info("请求头中获取");
      request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
      request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
      request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
      return id;
    } else {
      log.info("默认方式获取sessionId");
      // 否则按默认规则从cookie取sessionId
      return super.getSessionId(request, response);
    }
  }
 
}

3.3 实现前后端分离

在请求没有session的时候,请求拦截,但是不跳转到login.jsp,而是自己返回Json数据,就需要重新两个类。FormAuthenticationFilter和AuthenticatingFilter

1. /**
2.  * 
3.  */
4. package com.yuyi.config.shiro;
5. 
6. import java.io.PrintWriter;
7. 
8. import javax.servlet.ServletRequest;
9. import javax.servlet.ServletResponse;
10. 
11. import org.apache.shiro.authc.AuthenticationToken;
12. import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
13. import org.slf4j.Logger;
14. import org.slf4j.LoggerFactory;
15. 
16. import com.alibaba.fastjson.JSONObject;
17. 
18. /**
19.  * @author mcb 
20.  *
21.  * 2018年12月12日 下午5:38:41 
22.  */
23. public class FormAuthenticationFilterOverrite extends FormAuthenticationFilter{
24. 
25. 
26.   private static final Logger log = LoggerFactory.getLogger(FormAuthenticationFilterOverrite.class);
27. 
28. 
29.   /*
30.    *  重写时注意事项:
31.    *      1,没有session。调用FormAuthenticationFilter.onAccessDeny()方法。
32.    *      2,没有session,但是是LoginURL。调用AuthenticatingFilter.executeLogin()
33.    *                   认证成功,调用 AuthenticatingFilter中 onLoginSuccess(token, subject, request, response);
34.    *                   认证失败,调用 AuthenticatingFilter中 onLoginFailure(token, e, request, response);
35.    *                  在认证之前又开始进行了Token认证,所以要重写 createToken方法。
36.    *     
37.    * 
38.    */
39.   @Override
40.   protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
41.           if (this.isLoginRequest(request, response)) {
42.             log.info("--------------isLoginRequest--------------");
43.               if (this.isLoginSubmission(request, response)) {
44.                 log.info("--------------isLoginSubmission--------------");
45.                   if (log.isTraceEnabled()) {
46.                       log.trace("Login submission detected.  Attempting to execute login.");
47.                   }
48.                   AuthenticatingFilterOverride ao = new AuthenticatingFilterOverride();                
49.                   return ao.executeLogin(request, response);
50.               } else {
51.                   if (log.isTraceEnabled()) {
52.                       log.trace("Login page view.");
53.                   }
54.                   return true;
55.               }
56.           } else {
57.               if (log.isTraceEnabled()) {
58.                   log.trace("Attempting to access a path which requires authentication.  Forwarding to the Authentication url [" + this.getLoginUrl() + "]");
59.               }
60. 
61.               response.setContentType("application/json");
62.               response.setCharacterEncoding("UTF-8");
63.               PrintWriter out = response.getWriter();    
64.               JSONObject json = new JSONObject();
65.               json.put("no-session", "未登录,无法访问该地址");
66.               out.println(json);
67.               out.flush();
68.               out.close();
69.               return false;
70.           }
71.       }
72. 
73. 
74.   @Override
75.   public AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
76.           String username = getUsername(request);
77.           String password = getPassword(request);
78.           return createToken(username, password, request, response);
79.       }
80. 
81. }
/**
 * 
 */
package com.yuyi.config.shiro;
 
import java.io.IOException;
import java.io.PrintWriter;
 
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
 
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
import com.alibaba.fastjson.JSONObject;
 
/**
 * @author mcb 
 *
 * 2018年12月12日 下午5:17:00 
 */
public class AuthenticatingFilterOverride extends AuthenticatingFilter{
 
 
    private static final Logger log = LoggerFactory.getLogger(AuthenticatingFilterOverride.class);
 
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
      log.info("--------executeLogin---------");
      FormAuthenticationFilterOverrite formAuthen = new FormAuthenticationFilterOverrite();
        AuthenticationToken token = formAuthen.createToken(request, response);
        if (token == null) {
            String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
                    "must be created in order to execute a login attempt.";
            System.out.println("*******"+msg);
            throw new IllegalStateException(msg);
        }
        try {
          log.info("----------我进来进行核对了信息----------------");
            
          Subject subject = getSubject(request, response);
            
          subject.login(token);
            
            return this.onLoginSuccess(token, subject, request, response);
        } catch (AuthenticationException e) {
          System.out.println("-----onLoginFailure;---------");
            return this.onLoginFailure(token, e, request, response);
        }
    }
 
  
  @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
    log.info("AuthenticatingFilterOverride--------onLoginSuccess------");
    response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        return true;
    }
 
  
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
      log.info("AuthenticatingFilterOverride--------onLoginFailure------");
      response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        PrintWriter out = null;
        try {
            out = response.getWriter();
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        JSONObject json = new JSONObject();       
        String exc = e.getClass().getName();          
        if(exc.equals(UnknownAccountException.class.getName())){
          json.put("fail", "账户不存在");
        }
        if(exc.equals(IncorrectCredentialsException.class.getName())){
          System.out.println("=========");
          json.put("fail", "密码不正确");
        }
        out.println(json);
        out.flush();
        out.close();
        return false;
    }
 
  /* (非 Javadoc)
   * @see org.apache.shiro.web.filter.authc.AuthenticatingFilter#createToken(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
   */
  @Override
  protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
    // TODO 自动生成的方法存根
    
    return null;
  }
 
  /* (非 Javadoc)
   * @see org.apache.shiro.web.filter.AccessControlFilter#onAccessDenied(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
   */
  @Override
  protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
    // TODO 自动生成的方法存根
    
    return false;
  }
  
 
}

3.4 设置过滤器

在设置过滤器的时候需要注意,

package com.yuyi.config.shiro;
 
import java.util.LinkedHashMap;
import java.util.Map;
 
import javax.servlet.Filter;
 
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
/**
 * @author mcb 
 *
 * 2018年12月10日 下午5:39:32 
 */
@Configuration
public class MyShiroFilter{
  
  
  private static final Logger log = LoggerFactory.getLogger(MyShiroFilter.class);
 
  @Bean
  public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    
    
      Map<String,Filter> map = new LinkedHashMap<String,Filter>();  
    map.put("authc",getFormAuthenticationFilter());
    Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
    filterChainDefinitionMap.put("/user/**", "anon");
        //配置在最后面    
    filterChainDefinitionMap.put("/**", "authc");
    //登录的URL接口(Shiro可以进行识别)
    shiroFilterFactoryBean.setLoginUrl("/user/login");
    shiroFilterFactoryBean.setSecurityManager(securityManager);
        //这个map中包含了上面自定义的信息,配置到setFilter中
    shiroFilterFactoryBean.setFilters(map);
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    
    return shiroFilterFactoryBean;
 
  }
  
  /**开启shiro aop注解支持. 
     * 使用代理方式;所以需要开启代码支持;
   * @param securityManager
   * @return
   */
  @Bean  
  public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {  
      AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();  
      authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);  
      return authorizationAttributeSourceAdvisor;  
  }  
  
  @Bean
  FormAuthenticationFilterOverrite getFormAuthenticationFilter(){
    
    FormAuthenticationFilterOverrite authenticating = new FormAuthenticationFilterOverrite();
  
    return authenticating;
  }
}

3.5 配置自己的Realm信息(暂时没有配置授权信息)

/**
 * 
 */
package com.yuyi.mcb.shiro;
 
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.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
 
import com.yuyi.mcb.service.UserService;
import com.yuyi.model.User;
 
/**
 * @author mcb 
 *
 * 2018年12月10日 下午3:43:26 
 */
 
public class MyShiroRealmService extends AuthorizingRealm{
 
  //日志
  
  private static final Logger log = LoggerFactory.getLogger(MyShiroRealmService.class);
 
  
  @Autowired
  @Qualifier("userService")
  private UserService userService;
  
  //认证
  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    
    // TODO 自动生成的方法存根
    String username = (String)token.getPrincipal();
    log.info("token带来的数据:  "+username);
 
    String passwordDataSource = userService.findPass(username);
    log.info("从数据库中查询到的数据密码:{}",passwordDataSource);
    User user = userService.getUserByUsername(username);
    log.info("user:{}",user);
    
    SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
        user, //用户对象--数据库
              user.getPassword(), //密码--数据库
              ByteSource.Util.bytes(user.getSalt()),
              getName()  //realm name
        );
    return simpleAuthenticationInfo;  
  }
  
  
  //授权
  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
    // TODO 自动生成的方法存根
    //改掉null
    //查询数据库获取角色和权限信息
    //SimpleAuthorizationInfo a = new SimpleAuthorizationInfo();
//    a.setRoles(roles);
    return null;
  
  }
}

四、登录认证测试

4.1 Controller类

(1)/user/login

/**
 * 
 */
package com.yuyi.mcb.controller;
 
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.authz.annotation.RequiresRoles;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
 
import com.alibaba.fastjson.JSONObject;
 
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisShardInfo;
 
/**
 * @author mcb
 *
 *         2018年12月10日 下午2:13:02
 */
@RestController
@RequestMapping("/user")
public class LoginController {
 
  Logger logger = LoggerFactory.getLogger(getClass());
 
  @PostMapping("/login")
  public JSONObject login(@RequestParam String username, @RequestParam String password) {
 
    Subject subject = SecurityUtils.getSubject();
    JSONObject json = new JSONObject();
    Session session = subject.getSession();
    String sessionId = (String) session.getId();
    json.put("sessionId", sessionId);
 
    return json;
  }
 
}

(2)/out/logout

/**
 * 
 */
package com.yuyi.mcb.controller;
 
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.authz.annotation.RequiresUser;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisShardInfo;
 
/**
 * @author mcb 
 *
 * 2018年12月12日 上午11:28:36 
 */
@RestController
@RequestMapping("/out")
public class LogOutController {
  
  
  Logger logger = LoggerFactory.getLogger(getClass());
 
  @PostMapping("/logout")
  public void logout(){
    Subject subject = SecurityUtils.getSubject();
    Session session = subject.getSession();
    
    String sessionId = (String)session.getId();
    logger.info("sessionId{}",sessionId);
    JedisShardInfo  shardInfo = new JedisShardInfo("redis://192.168.1.234:6379"); 
    shardInfo.setPassword("123456");
    Jedis jedis = new Jedis(shardInfo);
    long jedis_key = jedis.del("shiro:session:"+sessionId); 
    logger.info("jedis_key{}",jedis_key); 
    logger.info("--------数据已经删除--------"); 
 
  }
 
}

(3)自定义测试接口

/**
 * 
 */
package com.yuyi.mcb.controller;
 
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
 
/**
 * @author mcb 
 *
 * 2018年12月12日 下午3:34:31 
 */
@RestController
public class UserController {
 
  @GetMapping("/userlist")
  @ResponseBody
  public String getUser(){
    
    
    return "user";
    
  }
  
}

4.2 测试结果

(1)登录测试

A。post:  http://localhost:8080/user/login?username=张三&password=123456

发送正确的登录信息,返回sessionId的Json值。

B。查看Redis数据库

C。后台打印:

 

4.2 退成登录测试

post:http://localhost:8080/out/logout

后台打印结果:

4.3 不正确密码登录

post: http://localhost:8080/user/login?username=张三&password=123455

4.4 没有登录直接请求接口,也就是没有session。

OK......完成。当然看似代码很多,其实里面重要的内容就是那些前后端分离时需要重写shiro内部的一些类,比较费劲。后期还有授权的代码。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
1月前
|
NoSQL Java API
springboot项目Redis统计在线用户
通过本文的介绍,您可以在Spring Boot项目中使用Redis实现在线用户统计。通过合理配置Redis和实现用户登录、注销及统计逻辑,您可以高效地管理在线用户。希望本文的详细解释和代码示例能帮助您在实际项目中成功应用这一技术。
37 4
|
1月前
|
消息中间件 NoSQL Java
Spring Boot整合Redis
通过Spring Boot整合Redis,可以显著提升应用的性能和响应速度。在本文中,我们详细介绍了如何配置和使用Redis,包括基本的CRUD操作和具有过期时间的值设置方法。希望本文能帮助你在实际项目中高效地整合和使用Redis。
53 2
|
2月前
|
缓存 NoSQL Java
Spring Boot与Redis:整合与实战
【10月更文挑战第15天】本文介绍了如何在Spring Boot项目中整合Redis,通过一个电商商品推荐系统的案例,详细展示了从添加依赖、配置连接信息到创建配置类的具体步骤。实战部分演示了如何利用Redis缓存提高系统响应速度,减少数据库访问压力,从而提升用户体验。
123 2
|
2月前
|
NoSQL Java Redis
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
这篇文章介绍了如何使用Spring Boot整合Apache Shiro框架进行后端开发,包括认证和授权流程,并使用Redis存储Token以及MD5加密用户密码。
39 0
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
|
1月前
|
JavaScript NoSQL Java
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
45 0
|
2月前
|
存储 缓存 NoSQL
数据的存储--Redis缓存存储(一)
数据的存储--Redis缓存存储(一)
100 1
|
2月前
|
消息中间件 缓存 NoSQL
Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。
【10月更文挑战第4天】Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。随着数据增长,有时需要将 Redis 数据导出以进行分析、备份或迁移。本文详细介绍几种导出方法:1)使用 Redis 命令与重定向;2)利用 Redis 的 RDB 和 AOF 持久化功能;3)借助第三方工具如 `redis-dump`。每种方法均附有示例代码,帮助你轻松完成数据导出任务。无论数据量大小,总有一款适合你。
78 6
|
1月前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
1月前
|
存储 缓存 NoSQL
【赵渝强老师】基于Redis的旁路缓存架构
本文介绍了引入缓存后的系统架构,通过缓存可以提升访问性能、降低网络拥堵、减轻服务负载和增强可扩展性。文中提供了相关图片和视频讲解,并讨论了数据库读写分离、分库分表等方法来减轻数据库压力。同时,文章也指出了缓存可能带来的复杂度增加、成本提高和数据一致性问题。
【赵渝强老师】基于Redis的旁路缓存架构
|
1月前
|
缓存 NoSQL Redis
Redis 缓存使用的实践
《Redis缓存最佳实践指南》涵盖缓存更新策略、缓存击穿防护、大key处理和性能优化。包括Cache Aside Pattern、Write Through、分布式锁、大key拆分和批量操作等技术,帮助你在项目中高效使用Redis缓存。
242 22
下一篇
DataWorks