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
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。