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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 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
目录
相关文章
|
3天前
|
安全 Java Apache
SpringBoot+Shiro(一)
SpringBoot+Shiro(一)
|
1月前
|
缓存 NoSQL Java
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】
这篇文章是关于如何在SpringBoot应用中整合Redis并处理分布式场景下的缓存问题,包括缓存穿透、缓存雪崩和缓存击穿。文章详细讨论了在分布式情况下如何添加分布式锁来解决缓存击穿问题,提供了加锁和解锁的实现过程,并展示了使用JMeter进行压力测试来验证锁机制有效性的方法。
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】
|
29天前
|
编解码 NoSQL Java
使用Spring Boot + Redis 队列实现视频文件上传及FFmpeg转码的技术分享
【8月更文挑战第30天】在当前的互联网应用中,视频内容的处理与分发已成为不可或缺的一部分。对于视频平台而言,高效、稳定地处理用户上传的视频文件,并对其进行转码以适应不同设备的播放需求,是提升用户体验的关键。本文将围绕使用Spring Boot结合Redis队列技术来实现视频文件上传及FFmpeg转码的过程,分享一系列技术干货。
77 3
|
22小时前
|
JSON NoSQL Java
redis的java客户端的使用(Jedis、SpringDataRedis、SpringBoot整合redis、redisTemplate序列化及stringRedisTemplate序列化)
这篇文章介绍了在Java中使用Redis客户端的几种方法,包括Jedis、SpringDataRedis和SpringBoot整合Redis的操作。文章详细解释了Jedis的基本使用步骤,Jedis连接池的创建和使用,以及在SpringBoot项目中如何配置和使用RedisTemplate和StringRedisTemplate。此外,还探讨了RedisTemplate序列化的两种实践方案,包括默认的JDK序列化和自定义的JSON序列化,以及StringRedisTemplate的使用,它要求键和值都必须是String类型。
redis的java客户端的使用(Jedis、SpringDataRedis、SpringBoot整合redis、redisTemplate序列化及stringRedisTemplate序列化)
|
1月前
|
Web App开发 前端开发 关系型数据库
基于SpringBoot+Vue+Redis+Mybatis的商城购物系统 【系统实现+系统源码+答辩PPT】
这篇文章介绍了一个基于SpringBoot+Vue+Redis+Mybatis技术栈开发的商城购物系统,包括系统功能、页面展示、前后端项目结构和核心代码,以及如何获取系统源码和答辩PPT的方法。
|
30天前
|
缓存 NoSQL Java
惊!Spring Boot遇上Redis,竟开启了一场缓存实战的革命!
【8月更文挑战第29天】在互联网时代,数据的高速读写至关重要。Spring Boot凭借简洁高效的特点广受开发者喜爱,而Redis作为高性能内存数据库,在缓存和消息队列领域表现出色。本文通过电商平台商品推荐系统的实战案例,详细介绍如何在Spring Boot项目中整合Redis,提升系统响应速度和用户体验。
49 0
|
1月前
|
缓存 NoSQL Java
【Azure Redis 缓存】定位Java Spring Boot 使用 Jedis 或 Lettuce 无法连接到 Redis的网络连通性步骤
【Azure Redis 缓存】定位Java Spring Boot 使用 Jedis 或 Lettuce 无法连接到 Redis的网络连通性步骤
|
14天前
|
canal 缓存 NoSQL
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
根据对一致性的要求程度,提出多种解决方案:同步删除、同步删除+可靠消息、延时双删、异步监听+可靠消息、多重保障方案
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
|
1月前
|
缓存 NoSQL Redis
【Azure Redis 缓存】Redission客户端连接Azure:客户端出现 Unable to send PING command over channel
【Azure Redis 缓存】Redission客户端连接Azure:客户端出现 Unable to send PING command over channel
|
30天前
|
缓存 NoSQL Java
Redis深度解析:解锁高性能缓存的终极武器,让你的应用飞起来
【8月更文挑战第29天】本文从基本概念入手,通过实战示例、原理解析和高级使用技巧,全面讲解Redis这一高性能键值对数据库。Redis基于内存存储,支持多种数据结构,如字符串、列表和哈希表等,常用于数据库、缓存及消息队列。文中详细介绍了如何在Spring Boot项目中集成Redis,并展示了其工作原理、缓存实现方法及高级特性,如事务、发布/订阅、Lua脚本和集群等,帮助读者从入门到精通Redis,大幅提升应用性能与可扩展性。
58 0