SpringSecurity 实战项目(三)——动态管理Restful风格权限+JWT

文章目录

  • 源码地址:
        • security认证过程:
        • security授权过程:
        • JWT全面解读、详细使用步骤:
    • 重要
  • 一、分析
    • 配置拦截器
      • JWTAuthenticationFilter
      • JWTAuthorizationFilter
    • 配置SpringSecurity
    • JWT工具类
  • 二、 修改
    • 1. 修改permission表
    • 2. 实现 GrantedAuthority 接口
    • 3.修改 MyAccessDecisionManager 的decide 方法
    • 4. 修改MyInvocationSecurityMetadataSourceService 的getAttributes 方法
    • 5.添加JwtUser和LoginUser
    • 6.添加身份认证过滤器:JWTAuthenticationFilter
    • 7.添加鉴权过滤器:JJWTAuthorizationFilter
    • 8.添加JwtTokenUtils
    • 9.配置SpringSecurity,关闭csrf,添加两个过滤器
      • 身份认证过滤器:JWTAuthenticationFilter
      • 鉴权过滤器:JWTAuthorizationFilter
    • 10. 修改HomeController
  • 三、准备数据
  • 四、测试
  • 参考资料:

上一篇博客 SpringSecurity 实战项目(二)只是实现了用户、角色、权限的动态管理,但是其权限管理是有缺陷的,他不支持restful风格的接口权限管理,因为他无法区分客户端的请求方式。

本片博客是为了弥补此缺陷的,本篇博客将在 SpringSecurity 实战项目(二)的基础上进行修改使其支持restful 风格的接口的权限管理。


源码地址:

源码地址:https://gitee.com/zenghua3300/SpringSecurity
喜欢的小伙伴麻烦帮点一个star


如果是刚接触security的小伙伴,可以先看下这两篇基础博客

security认证过程:

《Spring Security认证过程》

security授权过程:

《Spring Security授权过程》

JWT全面解读、详细使用步骤:

JWT全面解读、详细使用步骤


重要

本文设计和代码是基于 以下博客(请点击)

SpringSecurity 实战项目(一)

SpringSecurity 实战项目(二)

进行修改。


一、分析

  1. 首先分析一下工作量吧,因为要支持 restful风格的接口,那么我们在判断用户是不是有权限访问的时候不仅要判断 url还要判断请求方式。 所以我门需要修改数据库表,因为我门的权限表还没有method字段。

  2. 由于要判断 urlmethod所以要在CustomUserService类的loadUserByUsername 方法中要添加 权限的urlmethod。但是SimpleGrantedAuthority只支持传入一个参数。 所以我门考虑要再写一个类 实现 GrantedAuthority接口,并在构造函数中传入两个参数。嘻嘻。

  3. 由于我们不仅要判断url还要 判断请求方法,所以当然要修改MyAccessDecisionManagerdecide方法的内容了。

因为:decide方法是判定是否拥有权限的决策方法 ,三个参数的含义分别为:
//authentication是释CustomUserService中循环添加到 GrantedAuthority对象中的权限信息集合,相当于是自己定义的匹配规则,项目中把权限规则存在数据库Sys_permission表中
//object包含客户端发起的请求的requset信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
//configAttributesMyInvocationSecurityMetadataSourcegetAttributes(Object object)这个方法返回的结果,此方法是为了判定用户请求的url是否在权限表中,如果在权限表中,则返回给decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。这个项目中直接全部都拦截,全部都交给decide 方法

在这里插入图片描述

我们发现object为当前请求的 url:/persons, 那么getAttributes方法就是使用当前的访问资源路径去匹配我们自己定义的匹配规则。

  1. 当然在 修改一下 MyInvocationSecurityMetadataSourceServicegetAttributes
    方法。//此方法是为了判定用户请求的url是否在权限表中,如果在权限表中,则返回给 decide
    方法,用来判定用户是否有此权限。如果不在权限表中则放行。
因为我不想每一次来了请求,都先要匹配一下权限表中的信息是不是包含此url,
我准备直接拦截,不管请求的url 是什么都直接拦截,
然后在MyAccessDecisionManager的decide 方法中做 拦截还是放行的决策。

配置拦截器

可以说到目前为止这是最复杂的一个步骤,其实搞清楚了还是挺简单的,网上挺多人都更倾向于使用shiro,但是偶尔也要尝试一下新东西的嘛,但是当时我在摸索的时候遇到挺多坑,当时也已经到了思考人生的地步了 框架不是为了简化开发吗!为什么!明明jwt加上权限框架是双倍的快乐!

回到正题,到底要怎么配置呢?使用过shiro的人会知道,鉴权的话需要自己实现一个realm,重写两个方法,第一是用户验证第二是鉴权。在spring-security中也不例外,这边需要实现两个过滤器。使用JWTAuthenticationFilter去进行用户账号的验证,使用JWTAuthorizationFilter去进行用户权限的验证。

JWTAuthenticationFilter

JWTAuthenticationFilter继承于UsernamePasswordAuthenticationFilter
该拦截器用于获取用户登录的信息,只需创建一个token并调用authenticationManager.authenticate()spring-security去进行验证就可以了,不用自己查数据库再对比密码了,这一步交给spring去操作。
这个操作有点像是shirosubject.login(new UsernamePasswordToken()),验证的事情交给框架。
下边会献上这一部分的代码。

JWTAuthorizationFilter

验证成功当然就是进行鉴权了,每一次需要权限的请求都需要检查该用户是否有该权限去操作该资源,当然这也是框架帮我们做的,那么我们需要做什么呢?很简单,只要告诉spring-security该用户是否已登录,是什么角色,拥有什么权限就可以了。
JWTAuthenticationFilter继承于BasicAuthenticationFilter,至于为什么要继承这个我也不太清楚了,这个我也是网上看到的其中一种实现,实在springSecurity苦手,不过我觉得不继承这个也没事呢(实现以下filter接口或者继承其他filter实现子类也可以吧)只要确保过滤器的顺序,JWTAuthorizationFilterJWTAuthenticationFilter后面就没问题了。

配置SpringSecurity

到这里基本操作都写好啦,现在就需要我们将这些辛苦写好的“组件”组合到一起发挥作用了,那就需要配置了。需要开启一下注解@EnableWebSecurity然后再继承一下WebSecurityConfigurerAdapter就可以啦,springboot就是可以为所欲为~

  1. 关闭csrf
  2. 添加restful风格的接口
  3. 接着就可以拿着Token去访问资源,实现单点登录

好了分析完了,接下来就是编码了。


JWT工具类

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>

二、 修改

1. 修改permission表

添加method字段,当然Permissionjava bean中 也要添加此属性和其get set方法。
SpringSecurity 实战项目(三)——动态管理Restful风格权限+JWT_第1张图片

  //请求方法
    private String method;
      public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

2. 实现 GrantedAuthority 接口

新建javaMyGrantedAuthority实现 GrantedAuthority接口

package com.us.example.service;

import org.springframework.security.core.GrantedAuthority;

/**
 * Created by yangyibo on 17/2/15.
 */
public class MyGrantedAuthority implements GrantedAuthority {

    private String url;
    private String method;

    public String getPermissionUrl() {
        return url;
    }

    public void setPermissionUrl(String permissionUrl) {
        this.url = permissionUrl;
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public MyGrantedAuthority(String url, String method) {
        this.url = url;
        this.method = method;
    }

    @Override
    public String getAuthority() {
        return this.url + ";" + this.method;
    }
}

CustomUserService类中使用MyGrantedAuthority

public UserDetails loadUserByUsername(String username) {
        SysUser user = userDao.findByUserName(username);
        if (user != null) {
            List<Permission> permissions = permissionDao.findByAdminUserId(user.getId());
            List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
            for (Permission permission : permissions) {
                if (permission != null && permission.getName() != null) {

                    GrantedAuthority grantedAuthority = new MyGrantedAuthority(permission.getUrl(), permission.getMethod());
                    grantedAuthorities.add(grantedAuthority);
                }
            }
            //返回JwtUser类型的对象
            return new JwtUser(user.getUsername(), user.getPassword(), grantedAuthorities);

        } else {
            throw new UsernameNotFoundException("admin: " + username + " do not exist!");
        }
    }

3.修改 MyAccessDecisionManager 的decide 方法

package com.us.example.service;

import com.us.example.security.UrlGrantedAuthority;
import org.apache.log4j.Logger;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;

/**
 */
@Service
public class MyAccessDecisionManager implements AccessDecisionManager {
    static Logger logger = Logger.getLogger(MyAccessDecisionManager.class.getName());

    //decide 方法是判定是否拥有权限的决策方法
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
        String url, method;
        if ("anonymousUser".equals(authentication.getPrincipal())
                || matchers("/images/**", request)
                || matchers("/js/**", request)
                || matchers("/css/**", request)
                || matchers("/fonts/**", request)
                || matchers("/", request)
                || matchers("/index.html", request)
                || matchers("/favicon.ico", request)
                || matchers("/login", request)) {
            return;
        } else {
            for (GrantedAuthority ga : authentication.getAuthorities()) {
                if (ga instanceof UrlGrantedAuthority) {
                    UrlGrantedAuthority urlGrantedAuthority = (UrlGrantedAuthority) ga;
                    url = urlGrantedAuthority.getPermissionUrl();
                    method = urlGrantedAuthority.getMethod();
                    if (matchers(url, request)) {
                        if (method.equals(request.getMethod()) || "ALL".equals(method)) {
                            return;
                        }
                    }
                }
            }
        }
        throw new AccessDeniedException("no right");
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
    private boolean matchers(String url, HttpServletRequest request) {
        AntPathRequestMatcher matcher = new AntPathRequestMatcher(url);
        if (matcher.matches(request)) {
            return true;
        }
        return false;
    }
}

4. 修改MyInvocationSecurityMetadataSourceService 的getAttributes 方法

package com.us.example.service;

import com.us.example.security.UrlConfigAttribute;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.*;

@Service
public class MyInvocationSecurityMetadataSourceService  implements
        FilterInvocationSecurityMetadataSource {

    //此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        final HttpServletRequest request = ((FilterInvocation) object).getRequest();
        Set<ConfigAttribute> allAttributes = new HashSet<>();
        ConfigAttribute configAttribute = new UrlConfigAttribute(request);
        allAttributes.add(configAttribute);
        return allAttributes;
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}

5.添加JwtUser和LoginUser

6.添加身份认证过滤器:JWTAuthenticationFilter

package com.us.example.filter;


import com.fasterxml.jackson.databind.ObjectMapper;
import com.us.example.domain.JwtUser;
import com.us.example.model.LoginUser;
import com.us.example.utils.JwtTokenUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    private ThreadLocal<Integer> rememberMe = new ThreadLocal<>();
    private AuthenticationManager authenticationManager;
    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
        super.setFilterProcessesUrl("/auth/login");
    }
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {
        LoginUser loginUser = new LoginUser();
        try {
            // 从表单中获取用户信息
            if(request.getContentType().equals("application/x-www-form-urlencoded")){
                String username = request.getParameter("username");
                String password = request.getParameter("password");

                loginUser.setUsername(username);
                loginUser.setPassword(password);
                loginUser.setRememberMe(1);
            }else{
                // 从输入流中获取用户信息
                InputStream inputStream= request.getInputStream();
                loginUser = new ObjectMapper().readValue(inputStream, LoginUser.class);
            }
            rememberMe.set(loginUser.getRememberMe());
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword(), new ArrayList<>())
            );
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
    // 成功验证后调用的方法
    // 如果验证成功,就生成token并返回
    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {
        System.out.println(authResult.getPrincipal().toString());
        JwtUser jwtUser = (JwtUser) authResult.getPrincipal();
        System.out.println("jwtUser:" + jwtUser.toString());
        boolean isRemember = rememberMe.get() == 1;
        List permissionurl_methodList = new ArrayList();
        String role ="";
        Collection<? extends GrantedAuthority> authorities = jwtUser.getAuthorities();
        for (GrantedAuthority authority : authorities){
            role = authority.getAuthority();
            permissionurl_methodList.add(role);
        }
        String token = JwtTokenUtils.createToken(jwtUser.getUsername(), permissionurl_methodList, isRemember);
//        String token = JwtTokenUtils.createToken(jwtUser.getUsername(), false);
        // 返回创建成功的token
        // 但是这里创建的token只是单纯的token
        // 按照jwt的规定,最后请求的时候应该是 `Bearer token`
        response.setHeader("token", JwtTokenUtils.TOKEN_PREFIX + token);
    }
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        response.getWriter().write("authentication failed, reason: " + failed.getMessage());
    }
}

7.添加鉴权过滤器:JJWTAuthorizationFilter

package com.us.example.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.us.example.exception.TokenIsExpiredException;
import com.us.example.security.UrlGrantedAuthority;
import com.us.example.utils.JwtTokenUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
    UrlGrantedAuthority urlGrantedAuthority = new UrlGrantedAuthority();

    public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws IOException, ServletException {
        String tokenHeader = request.getHeader(JwtTokenUtils.TOKEN_HEADER);
        // 如果请求头中没有Authorization信息则直接放行了
        if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtils.TOKEN_PREFIX)) {
            chain.doFilter(request, response);
            return;
        }
        // 如果请求头中有token,则进行解析,并且设置认证信息
        try {
            SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
        } catch (TokenIsExpiredException e) {
            //返回json形式的错误信息
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            String reason = "统一处理,原因:" + e.getMessage();
            response.getWriter().write(new ObjectMapper().writeValueAsString(reason));
            response.getWriter().flush();
            return;
        }
        super.doFilterInternal(request, response, chain);
    }
    // 这里从token中获取用户信息并新建一个token
    private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) throws TokenIsExpiredException {
        String token = tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, "");
        boolean expiration = JwtTokenUtils.isExpiration(token);
        if (expiration) {
            throw new TokenIsExpiredException("token超时了");
        } else {
            String username = JwtTokenUtils.getUsername(token);
            List role = JwtTokenUtils.getUserRole(token);
            List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
            //把role里的值取出来,然后组装成UrlGrantedAuthority
            for (int i =0;i<role.size();i++) {
                if (role.get(i) != null) {
                    urlGrantedAuthority = SplitRole((String)role.get(i));
                    GrantedAuthority grantedAuthority = new UrlGrantedAuthority(urlGrantedAuthority.getPermissionUrl(),urlGrantedAuthority.getMethod());
                    grantedAuthorities.add(grantedAuthority);
                }
            }
            if (username != null) {
                return new UsernamePasswordAuthenticationToken(username, null, grantedAuthorities);
            }
        }
        return null;
    }
    private UrlGrantedAuthority SplitRole(String urlG){

        String[] roleString = StringUtils.split(urlG,";");
        urlGrantedAuthority.setPermissionUrl(roleString[0]);
        urlGrantedAuthority.setMethod(roleString[1]);

        return urlGrantedAuthority;
    }
}

8.添加JwtTokenUtils

package com.us.example.utils;

import com.us.example.exception.TokenIsExpiredException;
import com.us.example.filter.JWTAuthenticationFilter;
import io.jsonwebtoken.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;

public class JwtTokenUtils {
    private static final Logger logger = LoggerFactory.getLogger(JWTAuthenticationFilter.class);
    public static final String TOKEN_HEADER = "Authorization";
    public static final String TOKEN_PREFIX = "Bearer ";
    private static final String SECRET = "jwtsecretdemo";
    private static final String ISS = "echisan";

    // 角色的key
    private static final String PERMISSIONURL_METHOD = "permissionurl_method";

    // 过期时间是3600秒,既是1个小时
    private static final long EXPIRATION = 3600L;

    // 选择了记住我之后的过期时间为7天
    private static final long EXPIRATION_REMEMBER = 604800L;

    // 创建token
    public static String createToken(String username, List permissionurl_method, boolean isRememberMe) {
        long expiration = isRememberMe ? EXPIRATION_REMEMBER : EXPIRATION;
        HashMap<String, Object> map = new HashMap<>();
        map.put(PERMISSIONURL_METHOD, permissionurl_method);
        return Jwts.builder()
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .setClaims(map)
                .setIssuer(ISS)
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
                .compact();
    }
    // 从token中获取用户名
    public static String getUsername(String token) throws TokenIsExpiredException {
        return getTokenBody(token).getSubject();
    }

    // 获取用户角色
    public static List getUserRole(String token) throws TokenIsExpiredException {
        return (List)getTokenBody(token).get(PERMISSIONURL_METHOD);
    }

    // 是否已过期
    public static boolean isExpiration(String token) {
        try {
            return getTokenBody(token).getExpiration().before(new Date());
        } catch (ExpiredJwtException e) {
            return true;
        } catch (TokenIsExpiredException e) {
            e.printStackTrace();
            return true;
        }
    }

    private static Claims getTokenBody(String token) throws TokenIsExpiredException {
        try{
            return Jwts.parser()
                    .setSigningKey(SECRET)
                    .parseClaimsJws(token.replace("Bearer ", ""))
                    .getBody();
        } catch (ExpiredJwtException e) {
            logger.error("Token已过期: {} " + e);
            throw new TokenIsExpiredException("Token已过期");
        } catch (UnsupportedJwtException e) {
            logger.error("Token格式错误: {} " + e);
            throw new TokenIsExpiredException("Token格式错误");
        } catch (MalformedJwtException e) {
            logger.error("Token没有被正确构造: {} " + e);
            throw new TokenIsExpiredException("Token没有被正确构造");
        } catch (SignatureException e) {
            logger.error("签名失败: {} " + e);
            throw new TokenIsExpiredException("签名失败");
        } catch (IllegalArgumentException e) {
            logger.error("非法参数异常: {} " + e);
            throw new TokenIsExpiredException("非法参数异常");
        }
    }
}

9.配置SpringSecurity,关闭csrf,添加两个过滤器

身份认证过滤器:JWTAuthenticationFilter

鉴权过滤器:JWTAuthorizationFilter

关于 什么是csrf请看这篇博客

spring security CSRF 问题

修改 WebSecurityConfigconfigure(HttpSecurity http) 方法 ,添加 .csrf().disable();
添加两个过滤器JWTAuthenticationFilterJWTAuthorizationFilter

现在就需要我们将这些辛苦写好的“组件”组合到一起发挥作用了,那就需要配置了。需要开启一下注解@EnableWebSecurity然后再继承一下WebSecurityConfigurerAdapter就可以啦,springboot就是可以为所欲为~
好了编码部分完成了

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyFilterSecurityInterceptor myFilterSecurityInterceptor;

    @Autowired
    private  CustomUserService customUserService;

    //这里使用BCryptPasswordEncoder加密
    @Autowired
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(customUserService).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/css/**", "/js/**","/images/**", "**/favicon.ico","/login.html").permitAll()
                .anyRequest().authenticated() //任何请求,登录后可以访问
                .and()
                .formLogin()
                .loginPage("/login")
                .failureUrl("/login?error")
                .permitAll()
                .and()
                .logout()
                .permitAll() //注销行为任意访问
                .and()
                .addFilter(new JWTAuthenticationFilter(authenticationManager()))
//                .exceptionHandling().authenticationEntryPoint(new JWTAuthenticationEntryPoint())
//                .accessDeniedHandler(new JWTAccessDeniedHandler());      //添加无权限时的处理
                .addFilter(new JWTAuthorizationFilter(authenticationManager()));
        http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class)
                .csrf().disable();
    }
//        public void configure(WebSecurity web) throws Exception {
//        //解决静态资源被拦截的问题
//        web.ignoring().antMatchers("/css/**");
//    }
}

10. 修改HomeController

由于我们是要测试restful风格的权限,所以我门要有restful的接口

package com.us.example.controller;

import com.us.example.domain.Msg;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class HomeController {

    @RequestMapping("/")
    public String index(Model model){
        Msg msg =  new Msg("测试标题","测试内容","欢迎来到HOME页面,您拥有 ROLE_HOME 权限");
        model.addAttribute("msg", msg);
        return "home";
    }

    @RequestMapping("/admin")
    @ResponseBody
    public String hello(){
        return "hello admin";
    }

    @RequestMapping("/login")
    public String login(){
        return "login";
    }

    @RequestMapping(value = "/user", method = RequestMethod.GET)
    @ResponseBody
    public String getList(){
        return "hello getList";
    }
    
    @RequestMapping(value = "/user/hello", method = RequestMethod.GET)
    @ResponseBody
    public String getHelloList(){
        return "hello getList";
    }

    @RequestMapping(value = "/user", method = RequestMethod.POST)
    @ResponseBody
    public String save(){
        return "hello save";
    }


    @RequestMapping(value = "/user", method = RequestMethod.PUT)
    @ResponseBody
    public String update(){
        return "hello update";
    }
}

三、准备数据

数据库中添加测试数据,主要是权限表权限角色中间表

结果(角色1 可以访问 /user 下的所有接口, 角色2 只可以访问 /user 下的GET请求)

权限表:
SpringSecurity 实战项目(三)——动态管理Restful风格权限+JWT_第2张图片
权限角色中间表:(此处角色1拥有 权限 6权限6的方法为ALL 也就是角色6 可以访问所有路径为/user 的接口)
SpringSecurity 实战项目(三)——动态管理Restful风格权限+JWT_第3张图片


四、测试

启动项目,然后在postman中测试,

  1. 登录测试,因为JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter,默认的拦截登录路径是/auth/login,这里我们配置了可以自定义的登录路径,可以根据自己的需求来写。例如:/login。但这里security配置了不拦截登录路径/login,而我们前端表单提交的action/auth/login,这样才会把数据提交到我们配置的拦截器JWTAuthenticationFilter中,不然/login每次都会被security放过。

访问http://localhost:8081/auth1/login
因为/auth1/login不是登录路径,所以被security拦截,并且设置上是ROLE_ANONYMOUS,这是没有经过认证的角色,最后引导用户到登录页面登录
SpringSecurity 实战项目(三)——动态管理Restful风格权限+JWT_第4张图片
因为ROLE_ANONYMOUS是没有经过身份认证的角色,所以引导用户进行登录
SpringSecurity 实战项目(三)——动态管理Restful风格权限+JWT_第5张图片



    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
        super.setFilterProcessesUrl("/auth/login");
    }

访问http://localhost:8081/auth/login,登录成功后获得一个token
SpringSecurity 实战项目(三)——动态管理Restful风格权限+JWT_第6张图片
Token里边的信息
SpringSecurity 实战项目(三)——动态管理Restful风格权限+JWT_第7张图片

携带生成的token去访问http://localhost:8081/admin
SpringSecurity 实战项目(三)——动态管理Restful风格权限+JWT_第8张图片
改用put去访问http://localhost:8081/user
SpringSecurity 实战项目(三)——动态管理Restful风格权限+JWT_第9张图片
put方法访问成功 。


登录abel后,生成新的token,用新的token访问 user的所有权限,只有GET权限可以访问,put方法访问失败。
SpringSecurity 实战项目(三)——动态管理Restful风格权限+JWT_第10张图片
SpringSecurity 实战项目(三)——动态管理Restful风格权限+JWT_第11张图片


参考资料:

参考资料:
springBoot+springSecurity 动态管理Restful风格权限(三)
Springboot+Spring-Security+JWT+Redis实现restful Api的权限管理以及token管理(超详细用爱发电版)

你可能感兴趣的:(security,SpringSecurity)