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

/**
 * 
 */
package com.yuyi.config.shiro;

import java.io.PrintWriter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONObject;

/**
 * @author mcb 
 *
 * 2018年12月12日 下午5:38:41 
 */
public class FormAuthenticationFilterOverrite extends FormAuthenticationFilter{
	
	
	private static final Logger log = LoggerFactory.getLogger(FormAuthenticationFilterOverrite.class);
	

	/*
	 *  重写时注意事项:
	 *      1,没有session。调用FormAuthenticationFilter.onAccessDeny()方法。
	 *      2,没有session,但是是LoginURL。调用AuthenticatingFilter.executeLogin()
	 *                   认证成功,调用 AuthenticatingFilter中 onLoginSuccess(token, subject, request, response);
	 *                   认证失败,调用 AuthenticatingFilter中 onLoginFailure(token, e, request, response);
	 *     						  在认证之前又开始进行了Token认证,所以要重写 createToken方法。
	 *     
	 * 
	 */
	@Override
	protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
	        if (this.isLoginRequest(request, response)) {
	        	log.info("--------------isLoginRequest--------------");
	            if (this.isLoginSubmission(request, response)) {
	            	log.info("--------------isLoginSubmission--------------");
	                if (log.isTraceEnabled()) {
	                    log.trace("Login submission detected.  Attempting to execute login.");
	                }
	                AuthenticatingFilterOverride ao = new AuthenticatingFilterOverride();                
	                return ao.executeLogin(request, response);
	            } else {
	                if (log.isTraceEnabled()) {
	                    log.trace("Login page view.");
	                }
	                return true;
	            }
	        } else {
	            if (log.isTraceEnabled()) {
	                log.trace("Attempting to access a path which requires authentication.  Forwarding to the Authentication url [" + this.getLoginUrl() + "]");
	            }
	            
	            response.setContentType("application/json");
	            response.setCharacterEncoding("UTF-8");
	            PrintWriter out = response.getWriter();    
	            JSONObject json = new JSONObject();
	            json.put("no-session", "未登录,无法访问该地址");
	            out.println(json);
	            out.flush();
	            out.close();
	            return false;
	        }
	    }
	
	
	@Override
	public AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
	        String username = getUsername(request);
	        String password = getPassword(request);
	        return createToken(username, password, request, response);
	    }

}
/**
 * 
 */
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内部的一些类,比较费劲。后期还有授权的代码。

欢迎订阅关注公众号(JAVA和人工智能)

                                                           获取更多免费书籍、资源、视频资料 

      

文章超级链接:

 1,分布式系统详解--基础知识(概论)

 2,分布式系统详解--基础知识(线程)

 3,IDEA和Eclipse的比较

 4,IntelliJ IDEA(最新)安装-破解详解--亲测可用

 5,ipconfig中都是什么意思,如何配置虚拟机,网络知识你懂多少?

 6,【由浅入深】爬虫技术,值得收藏,来了解一下~

 7,Akka 简介及简单原理

 8,Spark-集群安装、部署、启动、测试(1.6.3)稳定版

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

10,分布式系统详解(Apache Hive 入门-简介)

11,Linux下Redis简介、安装、设置、启动

 

  • 7
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值