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 前期准备(点击打开页面)
(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和人工智能)
获取更多免费书籍、资源、视频资料
文章超级链接:
4,IntelliJ IDEA(最新)安装-破解详解--亲测可用
5,ipconfig中都是什么意思,如何配置虚拟机,网络知识你懂多少?
8,Spark-集群安装、部署、启动、测试(1.6.3)稳定版
9,Linux centos 6.5 - Mysql 安装 、卸载、修改密码、忘记密码 并异常处理