api Token Shiro权限控制

api Token Shiro权限控制

前后端分离,api通过token验证,后端用shiro权限控制。

token 采用放在header中。 首先获取token,ajax通过header回传token,保持同一会话。

pom.xml

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

  <groupId>com.tg.demo.shiroapi</groupId>
  <artifactId>demo-shiroapi</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>demo-shiroapi Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <!-- versions -->
    <spring.version>5.0.6.RELEASE</spring.version>
    <shiro.version>1.4.0</shiro.version>
    <lombok.version>1.16.20</lombok.version>
    <fastjson.version>1.2.47</fastjson.version>
    <jackson.version>2.9.5</jackson.version>
    <log4j2.version>2.10.0</log4j2.version>
    <slf4j.version>1.7.25</slf4j.version>
    <jedis.version>2.9.0</jedis.version>
    <ehcache.version>2.4.3</ehcache.version>
    <servlet-api.version>3.1.0</servlet-api.version>
    <commons-lang3.version>3.7</commons-lang3.version>

  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

    <!-- springmvc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>${spring.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>${spring.version}</version>
    </dependency>

    <!--添加aspectjweaver包 -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.5</version>
    </dependency>


    <!-- shiro -->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-web</artifactId>
      <version>${shiro.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring</artifactId>
      <version>${shiro.version}</version>
    </dependency>
    <dependency>
      <groupId>net.sf.ehcache</groupId>
      <artifactId>ehcache-core</artifactId>
      <version>${ehcache.version}</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>${fastjson.version}</version>
    </dependency>

    <!--日志-->
    <!--slf4j-->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>${slf4j.version}</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>${slf4j.version}</version>
    </dependency>
    <!-- log4j2 -->
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
      <version>${log4j2.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-api</artifactId>
      <version>${log4j2.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-slf4j-impl</artifactId>
      <version>${log4j2.version}</version>
    </dependency>

    <!--lombok -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>${lombok.version}</version>
    </dependency>

    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>${servlet-api.version}</version>
      <scope>provided</scope>
    </dependency>

    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>${commons-lang3.version}</version>
    </dependency>

    <!-- jackson -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>${jackson.version}</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>${jackson.version}</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>${jackson.version}</version>
    </dependency>

    <dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
      <version>${jedis.version}</version>
    </dependency>
  </dependencies>

  <build>
    <finalName>demo-shiroapi</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.0.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.7.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.20.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

Web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
​
  <filter>
    <filter-name>corsFilter</filter-name>
    <filter-class>com.tg.demo.shiroapi.filter.CorsFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>corsFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
​
  <!-- Shiro Filter -->
  <filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
      <param-name>targetFilterLifecycle</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
​
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:/applicationContext.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
​
  <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:dispatcher-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

 

dispatcher-servlet.xml

<context:component-scan base-package="com.tg.demo.shiroapi"></context:component-scan>
​
    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <constructor-arg index="0" value="utf-8" />
                <property name="supportedMediaTypes">
                    <list>
                        <value>application/json;charset=UTF-8</value>
                        <value>text/plain;charset=UTF-8</value>
                    </list>
                </property>
            </bean>
            <!--对日期进行转化的 -->
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                <property name="objectMapper">
                    <bean class="com.tg.demo.shiroapi.CustomObjectMapper">
                        <property name="dateFormat">
                            <bean class="java.text.SimpleDateFormat">
                                <constructor-arg type="java.lang.String" value="yyyy-MM-dd HH:mm:ss" />
                            </bean>
                        </property>
                    </bean>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
​
    <mvc:cors>
        <mvc:mapping path="/**" />
    </mvc:cors>
​
    <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>

mvc:cors配置对shiroFilter可能不起作用,走mvc之前被shiro拦截,所以配置了web.xml的corsFilter。

aop的开启必须放在spring-mvc/dispatcher-servlet,即dispather对应的xml为文件。

shiro.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
​
​
    <bean id="sampleRealm" class="com.tg.demo.shiroapi.shiro.realm.SampleRealm" ></bean>
​
    <!--自定义SessionManager-->
    <bean id="sessionManager" class="com.tg.demo.shiroapi.shiro.session.AccessTokenSessionManager" />
​
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="sampleRealm"/>
        <property name="sessionManager" ref="sessionManager"/>
    </bean>
​
    <bean id="login" class="com.tg.demo.shiroapi.shiro.filter.LoginFilter"></bean>
​
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <property name="loginUrl" value="/login" />
        <!--    TODO 待提取    -->
        <property name="successUrl" value="/" />
        <property name="unauthorizedUrl" value="/loginPrevent" />
​
        <!--    初始配置,现采用自定义 -->
        <property name="filterChainDefinitions" >
            <value>
                /login = anon
                /loginPrevent = anon
                /index = anon
                /index.jsp = anon
                /** = login
            </value>
        </property>
        <property name="filters">
            <util:map>
                <entry key="login" value-ref="login"></entry>
            </util:map>
        </property>
​
    </bean>
​
    <!-- Shiro生命周期处理器-->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
​
    <!--使用@RequiresRoles,@RequiresPermissions等注解-->
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
          depends-on="lifecycleBeanPostProcessor"/>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>
</beans>

自定义loginFilter,拦截返回json信息。

自定义SessionManager, AccessTokenSessionManager重写了getSessionId方法,先从header中获取Token作为sessionId。问题:篡改了sessionId,会导致corsFilter报异常,目前看不受影响,可以通过改写DAO的方式解决。

sampleRealm验证及权限

AccessTokenSessionManager

package com.tg.demo.shiroapi.shiro.session;
​
import org.apache.shiro.session.Session;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
​
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
​
public class AccessTokenSessionManager extends DefaultWebSessionManager {
​
    public static final String ACCESS_TOKEN = "Access-Token";
​
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        if(request instanceof HttpServletRequest){
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            String sessionId = httpServletRequest.getHeader(ACCESS_TOKEN);
            if(sessionId!=null) {
                try {
                    Session session = retrieveSessionFromDataSource(sessionId);
                    System.out.println("Access-Token:"+sessionId);
                    return sessionId;
                }catch (Exception e){
                }
            }
        }
        return super.getSessionId(request,response);
    }
​
​
}
​

 

SampleRealm

package com.tg.demo.shiroapi.shiro.realm;
​
import org.apache.shiro.authc.*;
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 SampleRealm extends AuthorizingRealm {
​
    
    public SampleRealm() {
        super();
    }
    /**
     *  认证信息,主要针对用户登录, 
     */
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken authcToken) throws AuthenticationException {
        
        UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
        // get from db password 123
        if(!("admin".equals(token.getUsername())&&"123".equals(new String(token.getPassword())))){
            throw new AccountException("帐号或密码不正确!");
        }
​
        return new SimpleAuthenticationInfo(token,token.getPassword(), getName());
    }
​
     /** 
     * 授权 
     */  
    @Override  
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {  
        
//      Long userId = TokenManager.getUserId();
        SimpleAuthorizationInfo info =  new SimpleAuthorizationInfo();
        //根据用户ID查询角色(role),放入到Authorization里。
//      Set<String> roles = roleService.findRoleByUserId(userId);
//      info.setRoles(roles);
//      //根据用户ID查询权限(permission),放入到Authorization里。
//      Set<String> permissions = permissionService.findPermissionByUserId(userId);
//      info.setStringPermissions(permissions);
//      info.addStringPermission("sys:hello");
        return info;  
    }  
​
​
}

LoginFilter

package com.tg.demo.shiroapi.shiro.filter;
​
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
​
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
​
​
@Slf4j
public class LoginFilter extends AccessControlFilter {
    final static Class<LoginFilter> CLASS = LoginFilter.class;
​
    public static final String ACCESS_TOKEN = "Access-Token";
​
    @Override
    protected boolean isAccessAllowed(ServletRequest request,
            ServletResponse response, Object mappedValue) throws Exception {
        if(isLoginRequest(request, response)){// && isEnabled()
            return Boolean.TRUE;
        }
​
        Subject subject = getSubject(request, response);
        return subject.isAuthenticated();
​
    }
​
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
​
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("login_status", "300");
        jsonObject.put("message", "无效的Token.");
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write(jsonObject.toJSONString());
​
        return Boolean.FALSE ;
    }
    
​
}
​LoginController
package com.tg.demo.shiroapi.controller;
​
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
​
@RestController
@RequestMapping("/")
public class LoginController{
​
    public static final String RESULT = "result";
    public static final String MESSAGE = "message";
    @RequestMapping("/login")
    public JSONObject login(String userName,String password){
        JSONObject jsonObject = new JSONObject();
        if(StringUtils.isNoneEmpty(userName,password)) {
            UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
            Subject subject = SecurityUtils.getSubject();
            Session session = subject.getSession();
            System.out.println(session.getId());
​
            try {
                subject.login(token);
                jsonObject.put("token", session.getId());
                jsonObject.put(RESULT, 1);
                jsonObject.put(MESSAGE, "登录成功");
            } catch (AuthenticationException e) {
                jsonObject.put(RESULT, 0);
                jsonObject.put(MESSAGE, "登录失败");
                e.printStackTrace();
            } catch (Exception e) {
                jsonObject.put(RESULT, 0);
                jsonObject.put(MESSAGE, "登录失败");
                e.printStackTrace();
            }
        }else{
            jsonObject.put(RESULT,0);
            jsonObject.put(MESSAGE,"登录失败");
        }
​
        return jsonObject;
    }
​
    @RequestMapping("/loginPrevent")
    public JSONObject needLogin(){
        JSONObject jsonObject = new JSONObject();
​
        jsonObject.put(RESULT,0);
        jsonObject.put(MESSAGE,"没有登录");
        return jsonObject;
    }
​
​
}
​

 

 

 

测试js

var token="";
    function getToken() {
        $.ajax({
            // url:'http://localhost:8081/login?userName=admin&password=123',
            url:'http://localhost:8081/login?userName=admin&password=123',
            type:'get',
            // beforeSend: function (xhr) {
            //   xhr.setRequestHeader("Access-Token", "b21fd4c6-057a-4953-a584-c882dce08955");
            // },
            success: function(data) {
                console.log(data);
                token = data.token;
            }
        });
    }
    function hello() {
        $.ajax({
            // url:'http://localhost:8081/login?userName=admin&password=123',
            url:'http://localhost:8081/hello',
            type:'get',
            beforeSend:  function(xhr) {
                xhr.setRequestHeader("Access-Token", token);
            },
            success:function (data) {
                console.log(data);
            }
        });
    }

完整代码:https://github.com/lvzhyt/demo-shiroapi

 

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值