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

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
简介: 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月前
|
安全 Java 数据库
安全无忧!在 Spring Boot 3.3 中轻松实现 TOTP 双因素认证
【10月更文挑战第8天】在现代应用程序开发中,安全性是一个不可忽视的重要环节。随着技术的发展,双因素认证(2FA)已经成为增强应用安全性的重要手段之一。本文将详细介绍如何在 Spring Boot 3.3 中实现基于时间的一次性密码(TOTP)双因素认证,让你的应用安全无忧。
71 5
|
5天前
|
NoSQL Java API
springboot项目Redis统计在线用户
通过本文的介绍,您可以在Spring Boot项目中使用Redis实现在线用户统计。通过合理配置Redis和实现用户登录、注销及统计逻辑,您可以高效地管理在线用户。希望本文的详细解释和代码示例能帮助您在实际项目中成功应用这一技术。
11 3
|
7天前
|
消息中间件 NoSQL Java
Spring Boot整合Redis
通过Spring Boot整合Redis,可以显著提升应用的性能和响应速度。在本文中,我们详细介绍了如何配置和使用Redis,包括基本的CRUD操作和具有过期时间的值设置方法。希望本文能帮助你在实际项目中高效地整合和使用Redis。
21 1
|
25天前
|
缓存 NoSQL Java
Spring Boot与Redis:整合与实战
【10月更文挑战第15天】本文介绍了如何在Spring Boot项目中整合Redis,通过一个电商商品推荐系统的案例,详细展示了从添加依赖、配置连接信息到创建配置类的具体步骤。实战部分演示了如何利用Redis缓存提高系统响应速度,减少数据库访问压力,从而提升用户体验。
64 2
|
12天前
|
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 的前后端分离的后台管理系统
28 0
|
1月前
|
NoSQL Java Redis
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
这篇文章介绍了如何使用Spring Boot整合Apache Shiro框架进行后端开发,包括认证和授权流程,并使用Redis存储Token以及MD5加密用户密码。
24 0
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
|
3月前
|
安全 Java 数据库
Spring Boot中集成 Shiro
本节主要介绍了 Shiro 安全框架与 Spring Boot 的整合。先介绍了 Shiro 的三大核心组件已经它们的作用;然后介绍了 Shiro 的身份认证、角色认证和权限认证;最后结合代码,详细介绍了 Spring Boot 中是如何整合 Shiro 的,并设计了一套测试流程,逐步分析 Shiro 的工作流程和原理,让读者更直观地体会出 Shiro 的整套工作流程。Shiro 使用的很广泛,希望读者将其掌握,并能运用到实际项目中。
|
Java Spring
spring boot 集成websocket与shiro的坑
spring boot 集成websocket与shiro的坑
382 0
|
6月前
|
前端开发 Java Spring
使用Spring Boot集成Shiro时出现了无法注入Service的问题
使用Spring Boot集成Shiro时出现了无法注入Service的问题
|
存储 安全 Java
SpringBoot集成shiro认证,实现Shiro认证的登录操作
SpringBoot集成shiro认证,实现Shiro认证的登录操作
232 0
SpringBoot集成shiro认证,实现Shiro认证的登录操作