shiro token 分析

1.ShiroConfig.java 定义匿名用户可以访问的资源

  filterMap.put("/webjars/**", "anon");
        filterMap.put("/druid/**", "anon");
        filterMap.put("/api/**", "anon");
        filterMap.put("/sys/login", "anon");
        filterMap.put("/**/*.css", "anon");
        filterMap.put("/**/*.js", "anon");
        filterMap.put("/**/*.html", "anon");
        filterMap.put("/fonts/**", "anon");
        filterMap.put("/plugins/**", "anon");
        filterMap.put("/swagger/**", "anon");
        filterMap.put("/favicon.ico", "anon");
        filterMap.put("/", "anon");
        filterMap.put("/**", "oauth2");           --除了anon,拦截其他所有请求

2.OAuth2Filter.java 基于shiro的全局过滤器

继承AuthenticatingFilter 实现createToken、isAccessAllowed、onAccessDenied、onLoginFailure等抽象方法

import com.google.gson.Gson;
import io.renren.common.utils.R;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpStatus;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class OAuth2Filter extends AuthenticatingFilter {

    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
        //获取请求token
        String token = getRequestToken((HttpServletRequest) request);

        if(StringUtils.isBlank(token)){
            return null;
        }
        return new OAuth2Token(token);
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        //获取请求token,如果token不存在,直接返回401
        String token = getRequestToken((HttpServletRequest) request);
        if(StringUtils.isBlank(token)){
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            String json = new Gson().toJson(R.error(HttpStatus.SC_UNAUTHORIZED, "invalid token"));
            httpResponse.getWriter().print(json);
            return false;
        }
        System.out.println("onAccessDenied-----------------------onAccessDenied");
        return executeLogin(request, response);
    }

    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setContentType("application/json;charset=utf-8");
        try {
            //处理登录失败的异常
            Throwable throwable = e.getCause() == null ? e : e.getCause();
            R r = R.error(HttpStatus.SC_UNAUTHORIZED, throwable.getMessage());

            String json = new Gson().toJson(r);
            httpResponse.getWriter().print(json);
        } catch (IOException e1) {

        }
        return false;
    }

    /**
     * 获取请求的token
     */
    private String getRequestToken(HttpServletRequest httpRequest){
        //从header中获取token
        String token = httpRequest.getHeader("token");

        //如果header中不存在token,则从参数中获取token
        if(StringUtils.isBlank(token)){
            token = httpRequest.getParameter("token");
        }
        return token;
    }
}

如果成功获得token 则继续调用父类中executeLogin方法,此方法实现如下

protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        AuthenticationToken token = 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.";
            throw new IllegalStateException(msg);
        }
        try {
       // 创建主题,然后继续调用Realm中的登入认证方法doGetAuthenticationInfo Subject subject
= getSubject(request, response); subject.login(token); return onLoginSuccess(token, subject, request, response); } catch (AuthenticationException e) { return onLoginFailure(token, e, request, response); } }

调用子类中的createToken方法获得token对象,将token对象赋值给shiro subject 对象,从而在后面的认证方法中获得token

3.将OAuth2Realm 注册到Shiro Seurity中,ShiroConfig.securityManager

 1 package io.renren.config;
 2 
 3 import io.renren.modules.sys.oauth2.OAuth2Filter;
 4 import io.renren.modules.sys.oauth2.OAuth2Realm;
 5 import org.apache.shiro.mgt.SecurityManager;
 6 import org.apache.shiro.session.mgt.SessionManager;
 7 import org.apache.shiro.spring.LifecycleBeanPostProcessor;
 8 import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
 9 import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
10 import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
11 import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
12 import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
13 import org.springframework.context.annotation.Bean;
14 import org.springframework.context.annotation.Configuration;
15 
16 import javax.servlet.Filter;
17 import java.util.HashMap;
18 import java.util.LinkedHashMap;
19 import java.util.Map;
20 
21 
22 @Configuration
23 public class ShiroConfig {
24 
25     @Bean("sessionManager")
26     public SessionManager sessionManager(){
27         DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
28         sessionManager.setSessionValidationSchedulerEnabled(true);
29         sessionManager.setSessionIdCookieEnabled(false);
30         System.out.println("获得sessionManager:" + sessionManager);
31         return sessionManager;
32     }
33 
34     @Bean("securityManager")
35     public SecurityManager securityManager(OAuth2Realm oAuth2Realm, SessionManager sessionManager) {
36         DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
37         securityManager.setRealm(oAuth2Realm);
38         securityManager.setSessionManager(sessionManager);
39         System.out.println("获得SecurityManager:" + securityManager);
40         return securityManager;
41     }
42 
43     @Bean("shiroFilter")
44     public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
45         ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
46         shiroFilter.setSecurityManager(securityManager);
47 
48         //oauth过滤
49         Map<String, Filter> filters = new HashMap<>();
50         filters.put("oauth2", new OAuth2Filter());
51         shiroFilter.setFilters(filters);
52 
53         Map<String, String> filterMap = new LinkedHashMap<>();
54         filterMap.put("/webjars/**", "anon");
55         filterMap.put("/druid/**", "anon");
56         filterMap.put("/api/**", "anon");
57         filterMap.put("/sys/login", "anon");
58         filterMap.put("/**/*.css", "anon");
59         filterMap.put("/**/*.js", "anon");
60         filterMap.put("/**/*.html", "anon");
61         filterMap.put("/fonts/**", "anon");
62         filterMap.put("/plugins/**", "anon");
63         filterMap.put("/swagger/**", "anon");
64         filterMap.put("/favicon.ico", "anon");
65         filterMap.put("/", "anon");
66         filterMap.put("/**", "oauth2");
67         shiroFilter.setFilterChainDefinitionMap(filterMap);
68         System.out.println("获得shiroFilter:" + shiroFilter);
69         return shiroFilter;
70     }
71 
72     @Bean("lifecycleBeanPostProcessor")
73     public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
74         return new LifecycleBeanPostProcessor();
75     }
76 
77     @Bean
78     public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
79         DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
80         proxyCreator.setProxyTargetClass(true);
81         return proxyCreator;
82     }
83 
84     @Bean
85     public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
86         AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
87         advisor.setSecurityManager(securityManager);
88         return advisor;
89     }
90 
91 }
View Code

4.每次请求都会先调用OAuth2Realm中的doGetAuthenticationInfo方法验证token的合法性,然后再调用doGetAuthorizationInfo验证权限

5.通过common.js判断当前客户端是否缓存了token,如果没有则跳转至login.html

//登录token
var token = localStorage.getItem("token");
if(token == 'null'){
    parent.location.href = baseURL + 'login.html';
}

6.登入页面输入用户名、密码之后 缓存token,并跳转至index.html

login: function () {
            var data = "username="+vm.username+"&password="+vm.password;
            $.ajax({
                type: "POST",
                url: baseURL + "sys/login",
                data: data,
                dataType: "json",
                success: function(r){
                    if(r.code == 0){//登录成功
                        localStorage.setItem("token", r.token);
                        parent.location.href ='index.html';
                    }else{
                        vm.error = true;
                        vm.errorMsg = r.msg;
                    }
                }
            });
        }
View Code

7.LoginController.java

/**
     * 登录
     */
    @RequestMapping(value = "/sys/login", method = RequestMethod.POST)
    public Map<String, Object> login(String username, String password)throws IOException {
        //用户信息
        SysUserEntity user = sysUserService.queryByUserName(username);

        //账号不存在、密码错误
        if(user == null || !user.getPassword().equals(new Sha256Hash(password, user.getSalt()).toHex())) {
            return R.error("账号或密码不正确");
        }

        //账号锁定
        if(user.getStatus() == 0){
            return R.error("账号已被锁定,请联系管理员");
        }

        //生成token,并保存到数据库
        R r = sysUserTokenService.createToken(user.getUserId());
        return r;
    }

8. 数据库token表结构,该表结构改成redis即可实现sso单点登入功能

  Field Type Comment
user_id bigint(20) NOT NULL  
  token varchar(100) NOT NULL token
  expire_time datetime NULL 过期时间
  update_time datetime NULL 更新时间
原文地址:https://www.cnblogs.com/rigid/p/7514708.html