`

106.3 Spring Boot之Shiro无状态(3)【从零开始学Spring Boot】

阅读更多

 

【视频&交流平台】

à SpringBoot视频

http://study.163.com/course/introduction.htm?courseId=1004329008&utm_campaign=commission&utm_source=400000000155061&utm_medium=share

à SpringCloud视频

http://study.163.com/course/introduction.htm?courseId=1004638001&utm_campaign=commission&utm_source=400000000155061&utm_medium=share

à Spring Boot源码

https://gitee.com/happyangellxq520/spring-boot

à Spring Boot交流平台

http://412887952-qq-com.iteye.com/blog/2321532

 

 

【原创文章,转载请注明出处】

上一节写到了,已经是无状态的,这节我们讲讲怎么加入请求控制拦截。

1)加入请求控制拦截代码

       以下这部分可能会有点难,需要集中注意力进行学习哦:

       首先我们先编写一个工具类,对参数信息处理——加密解密之消息摘要算法,具体代码如下:

package com.kfit.config;

 

import org.apache.commons.codec.binary.Hex;

import javax.crypto.Mac;

import javax.crypto.SecretKey;

import javax.crypto.spec.SecretKeySpec;

import java.util.List;

import java.util.Map;

 

/**

 * Java 加密解密之消息摘要算法

 * @author Angel --守护天使

 * @version v.0.1

 * @date 2017225

 */

public class HmacSHA256Utils {

 

    public static String digest(String key, String content) {

        try {

            Mac mac = Mac.getInstance("HmacSHA256");

            byte[] secretByte = key.getBytes("utf-8");

            byte[] dataBytes = content.getBytes("utf-8");

 

            SecretKey secret = new SecretKeySpec(secretByte, "HMACSHA256");

            mac.init(secret);

 

            byte[] doFinal = mac.doFinal(dataBytes);

            byte[] hexB = new Hex().encode(doFinal);

            return new String(hexB, "utf-8");

        } catch (Exception e) {

            throw new RuntimeException(e);

        }

    }

 

    @SuppressWarnings("unchecked")

    public static String digest(String key, Map<String, ?> map) {

        StringBuilder s = new StringBuilder();

        for(Object values : map.values()) {

            if(valuesinstanceof String[]) {

                for(String value : (String[])values) {

                    s.append(value);

                }

            } elseif(valuesinstanceof List) {

                for(String value : (List<String>)values) {

                    s.append(value);

                }

            } else {

                s.append(values);

            }

        }

        return digest(key, s.toString());

    }

}

 

       接下来我们需要一个AuthenticationToken保存我们的身份信息,比如用户名,客户端传入的消息摘要,还有客户端传入的参数map等,具体代码如下:

package com.kfit.config;

 

import java.util.Map;

 

import org.apache.shiro.authc.AuthenticationToken;

 

/**

 * 用于授权的Token对象:

 *

 * 用户身份即用户名;

 * 凭证即客户端传入的消息摘要。

 * @author Angel --守护天使

 * @version v.0.1

 * @date 2017225

 */

public class StatelessAuthenticationToken implements AuthenticationToken{

    private static final long serialVersionUID = 1L;

    private String username;//用户身份即用户名;

    private Map<String,?> params;//参数.

    private String clientDigest;//凭证即客户端传入的消息摘要。

   

    public StatelessAuthenticationToken() {

    }

    public StatelessAuthenticationToken(String username, Map<String, ?> params, String clientDigest) {

       super();

       this.username = username;

       this.params = params;

       this.clientDigest = clientDigest;

    }

   

    public StatelessAuthenticationToken(String username, String clientDigest) {

       super();

       this.username = username;

       this.clientDigest = clientDigest;

    }

 

    @Override

    public Object getPrincipal() {

       return username;

    }

 

    @Override

    public Object getCredentials() {

       return clientDigest;

    }

 

    public String getUsername() {

       return username;

    }

 

    publicvoid setUsername(String username) {

       this.username = username;

    }

 

    public Map<String, ?> getParams() {

       return params;

    }

 

    publicvoid setParams(Map<String, ?> params) {

       this.params = params;

    }

 

    public String getClientDigest() {

       return clientDigest;

    }

 

    publicvoid setClientDigest(String clientDigest) {

       this.clientDigest = clientDigest;

    }

}

 

       好了,准备工作好了之后,我们就可以开始编写核心的代码了,首先是第一个访问控制过滤器,拦截我们的请求,我们主要是处理onAccessDenied()方法,接收到请求的参数,组装成StatelessAuthenticationToken,然后委托为Realm进行处理,具体代码思路如下:

 
//1、客户端生成的消息摘要;
//2、客户端传入的用户身份;
//3、客户端请求的参数列表;
//4、生成无状态Token
//5、委托给Realm进行登录

 

       具体的代码如下:

package com.kfit.config;

 

import java.io.IOException;

import java.util.HashMap;

import java.util.Map;

 

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletResponse;

import org.apache.shiro.web.filter.AccessControlFilter;

 

/**

 * 访问控制过滤器

 * @author Angel --守护天使

 * @version v.0.1

 * @date 2017225

 */

public class StatelessAccessControlFilter extends AccessControlFilter{

 

    /**

     * 先执行:isAccessAllowed 再执行onAccessDenied

     *

     * isAccessAllowed:表示是否允许访问;mappedValue就是[urls]配置中拦截器参数部分,

     * 如果允许访问返回true,否则false

     *

     * 如果返回true的话,就直接返回交给下一个filter进行处理。

     * 如果返回false的话,回往下执行onAccessDenied

     */

    @Override

    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)

           throws Exception {

       System.out.println("StatelessAuthcFilter.isAccessAllowed()");

       return false;

    }

 

    /**

     * onAccessDenied:表示当访问拒绝时是否已经处理了;如果返回true表示需要继续处理;

     * 如果返回false表示该拦截器实例已经处理了,将直接返回即可。

     */

    @Override

    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {

       System.out.println("StatelessAuthcFilter.onAccessDenied()");

      

      

       //1、客户端生成的消息摘要

       String clientDigest = request.getParameter("digest");

      

       //2、客户端传入的用户身份

       String username = request.getParameter("username");

      

       //3、客户端请求的参数列表

       Map<String, String[]> params = new HashMap<String, String[]>(request.getParameterMap());

       params.remove("digest");//为什么要移除呢?签名或者消息摘要算法的时候不能包含digest.

      

       //4、生成无状态Token

       StatelessAuthenticationToken token = new StatelessAuthenticationToken(username,params,clientDigest);

//     UsernamePasswordToken token = new UsernamePasswordToken(username,clientDigest);

       try {

           //5、委托给Realm进行登录

           getSubject(request, response).login(token);

       } catch (Exception e) {

           e.printStackTrace();

           //6、登录失败

           onLoginFail(response);

           return false;//就直接返回给请求者.

       }

       return true;

    }

   

    //登录失败时默认返回401 状态码

    private void onLoginFail(ServletResponse response) throws IOException {

       HttpServletResponse httpResponse = (HttpServletResponse) response;

       httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

       httpResponse.getWriter().write("login error");

    }

 

}

 

       紧接着请求就到了我们的Realm代码,所以我们需要编写一个Realm来进行身份验证下,这里的核心就是获取到AccessControlFilter传递过来的StatelessAuthenticationToken中的参数进行消息摘要,然后生成对象SimpleAuthenticationInfo交给Shiro进行比对,具体代码如下:

package com.kfit.config;

 

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;

 

public class StatelessAuthorizingRealm extends AuthorizingRealm{

 

    /**

     * 仅支持StatelessToken 类型的Token

     * 那么如果在StatelessAuthcFilter类中返回的是UsernamePasswordToken,那么将会报如下错误信息:

     * Please ensure that the appropriate Realm implementation is configured correctly or

     * that the realm accepts AuthenticationTokens of this type.StatelessAuthcFilter.isAccessAllowed()

     */

    @Override

    publicboolean supports(AuthenticationToken token) {

       return token instanceof StatelessAuthenticationToken;

    }

   

    /**

     * 身份验证

     */

    @Override

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

       System.out.println("StatelessRealm.doGetAuthenticationInfo()");

      

       StatelessAuthenticationToken statelessToken = (StatelessAuthenticationToken)token;

       String username = (String)statelessToken.getPrincipal();//不能为null,否则会报错的.

      

       //根据用户名获取密钥(和客户端的一样)

       String key = getKey(username);

 

       //在服务器端生成客户端参数消息摘要

       String serverDigest = HmacSHA256Utils.digest(key, statelessToken.getParams());

      

      

       System.out.println(serverDigest+","+statelessToken.getCredentials());

       //然后进行客户端消息摘要和服务器端消息摘要的匹配

       SimpleAuthenticationInfo  authenticationInfo = new SimpleAuthenticationInfo(

              username,

              serverDigest,

              getName());

       return authenticationInfo;

    }

   

    /**

     * 授权

     */

    @Override

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

       System.out.println("StatelessRealm.doGetAuthorizationInfo()");

       //根据用户名查找角色,请根据需求实现

       String username = (String) principals.getPrimaryPrincipal();

       SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();

      

       //这里模拟admin账号才有role的权限.

       if("admin".equals(username)){

           authorizationInfo.addRole("admin");

       }

       return authorizationInfo;

    }

   

    //得到密钥,此处硬编码一个.

    private String getKey(String username) {

       return "andy123456";

    }

}

       剩下的就是将我们的这些配置到ShiroConfiguration类中(新加的代码有Add.4.x,具体代码如下:

package com.kfit.config;

 

import java.util.LinkedHashMap;

import java.util.Map;

 

import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;

import org.apache.shiro.mgt.DefaultSubjectDAO;

import org.apache.shiro.mgt.SecurityManager;

import org.apache.shiro.session.mgt.DefaultSessionManager;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;

import org.apache.shiro.web.mgt.DefaultWebSecurityManager;

import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

 

/**

 * shiro配置类.

 * @author Angel --守护天使

 * @version v.0.1

 * @date 2017225

 */

@Configuration

public class ShiroConfiguration {

   

    @Bean

    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){

       ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();

       factoryBean.setSecurityManager(securityManager);

      

       //Add.4.2.start

       factoryBean.getFilters().put("statelessAuthc", statelessAuthcFilter());

       //拦截器.

        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();

        filterChainDefinitionMap.put("/**", "statelessAuthc");

       factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

       //Add.4.2.end

      

       return factoryBean;

    }

   

    /**

     * shiro安全管理器:

     * 主要是身份认证的管理,缓存管理,cookie管理,

     * 所以在实际开发中我们主要是和SecurityManager进行打交道的

     * @return

     */

    @Bean

    public DefaultWebSecurityManager securityManager() {

        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

       

        //Add.2.2

        securityManager.setSubjectFactory(subjectFactory());

        //Add.2.5

        securityManager.setSessionManager(sessionManager());

        //Add.4.4

        securityManager.setRealm(statelessRealm());

        /*

         * 禁用使用Sessions 作为存储策略的实现,但它没有完全地禁用Sessions

         * 所以需要配合context.setSessionCreationEnabled(false);

         */

        //Add.2.3

        ((DefaultSessionStorageEvaluator)((DefaultSubjectDAO)securityManager.getSubjectDAO()).getSessionStorageEvaluator()).setSessionStorageEnabled(false);

       

       

        return securityManager;

    }

   

    /**

     * Add.2.1

     * subject工厂管理器.

     * @return

     */

    @Bean

    public DefaultWebSubjectFactory subjectFactory(){

       StatelessDefaultSubjectFactory subjectFactory = new StatelessDefaultSubjectFactory();

       return subjectFactory;

    }

   

    /**

     * Add.2.4

     * session管理器:

     * sessionManager通过sessionValidationSchedulerEnabled禁用掉会话调度器,

     * 因为我们禁用掉了会话,所以没必要再定期过期会话了。

     * @return

     */

    @Bean

    public DefaultSessionManager sessionManager(){

       DefaultSessionManager sessionManager = new DefaultSessionManager();

       sessionManager.setSessionValidationSchedulerEnabled(false);

       return sessionManager;

    }

   

   

    /**

     * Add.4.3

     * 自己定义的realm.

     * @return

     */

    @Bean

    public  StatelessAuthorizingRealm statelessRealm(){

       StatelessAuthorizingRealm realm = new StatelessAuthorizingRealm();

       return realm;

    }

   

   

    /**

     * Add.4.1

     * 访问控制器.

     * @return

     */

    @Bean

    public StatelessAccessControlFilter statelessAuthcFilter(){

       StatelessAccessControlFilter statelessAuthcFilter = new StatelessAccessControlFilter();

       return statelessAuthcFilter;

    }

   

}

       好了,到这里终于差不多了,赶紧测试下吧,访问地址:

http://127.0.0.1:8080/hello?username=admin&params1=love&params2=girl&digest=df7f1595bd5682638556072c8ccde5edadcd807a829373d21af38fb1bc707da7

这里的digest是根据参数生成的,必须为如下:

df7f1595bd5682638556072c8ccde5edadcd807a829373d21af38fb1bc707da7

否则就输入其它的直进行测试的话,就会看到login error。到这里基本算是完成了。

 

 

 

【视频&交流平台】

à Spring Boot实战篇之Shiro

http://study.163.com/course/introduction.htm?courseId=1004523002

 

à Spring Boot交流平台

http://412887952-qq-com.iteye.com/blog/2321532

 

 ======================================

Spring Boot Shiro视频实战篇【已更新】

======================================

 

适合人群

有Spring Boot基础的人群。

 

使用技术

(1)spring boot(整合框架)

(2)spring mvc

(3)spring data jpa(持久化操作)

(4)shiro(安全框架)

(5)thymeleaf(模板引擎)

(6)ehcache(缓存管理)

(7)mysql(数据库)

(8)js/css/img(静态资源使用)

9kaptcha(验证码库)

 

课程目录

1. Spring Boot Shiro介绍

 

2. Spring Boot 搭建无Shiro的框架

 

3. Spring Boot Shiro拦截

 

4. Spring Boot Shiro身份认证准备工作

 

5. Spring Boot Shiro身份认证

 

6. Spring Boot Shiro权限控制

 

7. Spring Boot Shiro缓存

 

8. Spring Boot Shiro记住密码

 

9. Spring Boot Shiro登录成功之后下载favicon.ico

 

10. Spring Boot 在thymeleaf使用shiro标签

 

11. Spring Boot Shiro密码加密算法

 

12.Spring Boot Shiro使用JS-CSS-IMG

 

13. Spring Boot Shiro限制登录尝试次数

 

14.Spring Boot Shiro 验证码

分享到:
评论
4 楼 u013990690 2018-03-30  
我和三楼是一样的问题,请问这个问题怎么解决呢?
3 楼 淡淡的伤你 2017-04-06  
shiro.config.subjectFactory.createSubject.SessionCreationEnabled.false
StatelessAuthcFilter.isAccessAllowed()
StatelessAuthcFilter.onAccessDenied()
StatelessRealm.doGetAuthenticationInfo()
df7f1595bd5682638556072c8ccde5edadcd807a829373d21af38fb1bc707da7,df7f1595bd5682638556072c8ccde5edadcd807a829373d21af38fb1bc707da7
shiro.config.subjectFactory.createSubject.SessionCreationEnabled.false
org.apache.shiro.authc.AuthenticationException: Authentication failed for token submission [cn.merryyou.config.StatelessAuthenticationToken@a973b9f].  Possible unexpected error? (Typical or expected login exceptions should extend from AuthenticationException).
	at org.apache.shiro.authc.AbstractAuthenticator.authenticate(AbstractAuthenticator.java:214)
	at org.apache.shiro.mgt.AuthenticatingSecurityManager.authenticate(AuthenticatingSecurityManager.java:106)
	at org.apache.shiro.mgt.DefaultSecurityManager.login(DefaultSecurityManager.java:270)
	at org.apache.shiro.subject.support.DelegatingSubject.login(DelegatingSubject.java:256)
	at cn.merryyou.config.StatelessAccessControlFilter.onAccessDenied(StatelessAccessControlFilter.java:57)
	at org.apache.shiro.web.filter.AccessControlFilter.onAccessDenied(AccessControlFilter.java:133)
	at org.apache.shiro.web.filter.AccessControlFilter.onPreHandle(AccessControlFilter.java:162)
	at org.apache.shiro.web.filter.PathMatchingFilter.isFilterChainContinued(PathMatchingFilter.java:203)
	at org.apache.shiro.web.filter.PathMatchingFilter.preHandle(PathMatchingFilter.java:178)
	at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:131)
	at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
	at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)
	at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:449)
	at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365)
	at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)
	at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)
	at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:383)
	at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362)
	at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
	at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:105)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:474)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:349)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:783)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:798)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1434)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.IllegalArgumentException: principal argument cannot be null.
	at org.apache.shiro.subject.SimplePrincipalCollection.add(SimplePrincipalCollection.java:104)
	at org.apache.shiro.subject.SimplePrincipalCollection.<init>(SimplePrincipalCollection.java:59)
	at org.apache.shiro.authc.SimpleAuthenticationInfo.<init>(SimpleAuthenticationInfo.java:74)
	at cn.merryyou.config.StatelessAuthorizingRealm.doGetAuthenticationInfo(StatelessAuthorizingRealm.java:67)
	at org.apache.shiro.realm.AuthenticatingRealm.getAuthenticationInfo(AuthenticatingRealm.java:568)
	at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doSingleRealmAuthentication(ModularRealmAuthenticator.java:180)
	at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doAuthenticate(ModularRealmAuthenticator.java:267)
	at org.apache.shiro.authc.AbstractAuthenticator.authenticate(AbstractAuthenticator.java:198)
	... 52 more
shiro.config.subjectFactory.createSubject.SessionCreationEnabled.false
StatelessAuthcFilter.isAccessAllowed()
StatelessAuthcFilter.onAccessDenied()
StatelessRealm.doGetAuthenticationInfo()
165a0e0e6b35e91913a821f3709032adea796c73e1c9be2dea96771b72bd15e2,null


为啥跑了两次,第二次 null
2 楼 淡淡的伤你 2017-04-06  
   
1 楼 飞天0407 2017-02-28  
博主,问一个问题,最后使用
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                username,
                serverDigest,
                getName());

进行验证,shiro是如何进行验证的呢

相关推荐

Global site tag (gtag.js) - Google Analytics