shiro、spring和JWT的集成

XML配置

1. 配置web.xml

  1. 配置springShiro.xml文件读取

    1
    2
    3
    4
    <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml classpath:springShiro.xml</param-value>
    </context-param>

    可以和applicationContext.xml一起写。

  2. 配置过滤器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <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>

    注意这里filter的名字必须为:shiroFilter。因为Shiro内部是直接读取名字的。

    配置springShiro.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    <?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:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <!-- proxy-target-class="true"强制使用CGLib代理,为false则spring会自动选择,否则事务不生效 -->
    <aop:aspectj-autoproxy proxy-target-class="true" />

    <!-- 配置relam -->
    <bean id="tidyRealm" class="priv.mw.shiro.TidyRealm"></bean>

    <!-- 配置权限管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="realms" ref="tidyRealm" />
    <property name="cacheManager" ref="cacheManager" />
    </bean>

    <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />

    <bean id="tidyFilter" class="priv.mw.filter.JWTFilter"/>

    <!-- 配置shiro的过滤器工厂类,id- shiroFilter要和我们在web.xml中配置的过滤器一致 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <!-- 调用我们配置的权限管理器 -->
    <property name="securityManager" ref="securityManager" />
    <property name="filters">
    <map>
    <entry key="tidyFilter" value-ref="tidyFilter"/>
    </map>
    </property>
    <property name="filterChainDefinitions">
    <value>
    /login/**=anon
    /register/**=anon
    /**=tidyFilter
    </value>
    </property>
    </bean>

    <!-- 配置shiro bean生命周期管理类 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
    </beans>

    其中最主要的是filter的配置,因为这里结合的是JWT,并不是基于session,所以并不需要shiro来帮助我们保存用户信息。

    然后就是ShiroFilterFactoryBean的配置。

    其中会配置securityManagerfilters,其中filterChainDefinitions是用来进行url权限地址认证:

    其中有几个点:

    • url中的*表示任意一级url匹配。比如:/url/*可以匹配/url/a或者/url/aaa,但不能匹配/url/a/aa因为产生2级目录了。
    • url中的**表示任意多级的url匹配。比如/url/**既可以匹配/url/a又可以匹配/url/a/aa
    • =后是需要的权限,可以是shiro定义的Filter,也可以是我们自己定义的Filter。其内置的有filter,并且有简写
      • anon—————org.apache.shiro.web.filter.authc.AnonymousFilter
      • authc————–org.apache.shiro.web.filter.authc.FormAuthenticationFilter
      • authcBasic———org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
      • logout————-org.apache.shiro.web.filter.authc.LogoutFilter
      • noSessionCreation–org.apache.shiro.web.filter.session.NoSessionCreationFilter
      • perms————–org.apache.shiro.web.filter.authz.PermissionAuthorizationFilter
      • port—————org.apache.shiro.web.filter.authz.PortFilter
      • rest—————org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
      • roles————–org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
      • ssl—————-org.apache.shiro.web.filter.authz.SslFilter
      • user—————org.apache.shiro.web.filter.authz.UserFilter

    这里还没有做授权,所以Realm并没有贴出来,本来应该是从token中拿出id,然后去查询数据库,看权限是否合法。

    可以在FormAuthenticationFilter中通过getSubject(servletRequest, servletResponse)获取subject来进行登录。

    tidyFilter

    下面是tidyFilter的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    public class JWTFilter extends FormAuthenticationFilter {

    ObjectMapper mapper = new ObjectMapper();
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
    HttpServletRequest req = (HttpServletRequest) request;
    String rawToken = req.getHeader("Authorization");
    response.setContentType("text/json;charset=utf-8");
    if(rawToken == null || "".equals(rawToken)){
    try {
    response.getWriter().println(mapper.writeValueAsString(Result.data("").msg("未认证,请先认证!").code(401)));
    return false;
    } catch (IOException e) {
    e.printStackTrace();
    return false;
    }
    }else {
    String token = rawToken.substring(7);
    boolean tokenValid = JWTUtils.checkToken(token);
    if(!tokenValid){
    try {
    response.getWriter().println(mapper.writeValueAsString(Result.data("").msg("认证已过期,请重新登录!").code(401)));
    return false;
    } catch (IOException e) {
    e.printStackTrace();
    return false;
    }
    }else {
    return true;
    }
    }
    }
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    HttpServletRequest req = (HttpServletRequest) request;
    String token = req.getHeader("Authorization");
    response.setContentType("text/json;charset=utf-8");
    if(token == null || "".equals(token)){
    return false;
    }else {
    boolean tokenValid = JWTUtils.checkToken(token);
    if(!tokenValid){
    return false;
    }else{
    return true;
    }
    }
    }

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
    return super.executeLogin(request, response);
    }
    }

    这个类就是用来验证header中携带的token信息,由于其是无状态的。所以直接通过工具类验证就行了。

    这个类继承了FormAuthenticationFilter,其两个方法:

    • isAccessAllowed:判断这个请求是否允许。
    • onAccessDenied:如果不允许,则引导用户进行认证。(此处是直接返回false,并返回未认证的JSON)

下面是一个简单的JWTUtils,有待加强:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class JWTUtils {

private static String key = "secret";
private static int aliveDay = 1;

public static boolean checkToken(String token){
try{
JWT.decode(token).getPayload();
return true;
}catch (Exception e){
return false;
}
}

public static String generateToken(HashMap<String, Object> map){
LocalDate localDate = LocalDate.now();
localDate.plusDays(aliveDay);

Algorithm algorithm = Algorithm.HMAC256(key);
String token = JWT.create()
.withPayload(map)
.withJWTId(UUID.randomUUID().toString())
.withExpiresAt(Date.from(localDate.atStartOfDay(ZoneOffset.ofHours(8)).toInstant()))
.sign(algorithm);
return token;
}

public static String parseTokenToName(String token){
if(checkToken(token)){
return JWT.decode(token).getClaim("name").asString();
}else{
return null;
}
}

public static Integer parseTokenToId(String token){
if(checkToken(token)){
return JWT.decode(token).getClaim("id").asInt();
}else{
return null;
}
}
}

class配置(待完成)

Powered by Hexo and Hexo-theme-hiker

Copyright © 2019 - 2024 My Wonderland All Rights Reserved.

UV : | PV :