新版security demo(一)认证&授权&拦截&权限校验

一、认证&授权&拦截:

1、pom:
<?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>org.example</groupId>
    <artifactId>security-jwt-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.4</version>
        <relativePath/>
    </parent>
<!--    <parent>-->
<!--        <groupId>org.springframework.boot</groupId>-->
<!--        <artifactId>spring-boot-starter-parent</artifactId>-->
<!--        <version>2.1.14.RELEASE</version>-->
<!--    </parent>-->

    <properties>
        <spring-security.version>6.2.3</spring-security.version>
    </properties>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
        </dependency>
        <!--commom-->
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.2</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--security-->
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-core</artifactId>
        <version>${spring-security.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
        <version>${spring-security.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-crypto</artifactId>
        <version>${spring-security.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <version>${spring-security.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-web</artifactId>
        <version>${spring-security.version}</version>
    </dependency>

    <!--jwt-->
    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.7.0</version>
    </dependency>

        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.6</version>
        </dependency>
    </dependencies>

    <build>
<!--        <finalName>my-service</finalName>-->
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
2、配置文件
server.port=2222
server.servlet.context-path=/securityDemo

my.redis.server.host= localhost
my.redis.server.port=6379
my.redis.server.jedis.pool.maxTotal=500
my.redis.server.jedis.pool.maxIdle=10
my.redis.server.jedis.pool.maxWaitMillis=5000
my.redis.server.jedis.pool.min-idle=5
my.redis.server.timeout=5000

3、启动类
package com.demo.security;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableMBeanExport;
import org.springframework.jmx.support.RegistrationPolicy;

@SpringBootApplication
//解决报错MXBean already registered with name org.apache.commons.pool2:type=GenericObjectPool,name=pool
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
public class SecurityApplication {

    public static void main(String[] args) {
        SpringApplication.run(SecurityApplication.class, args);

    }
}
4、config配置
(1)jedis
package com.demo.security.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.support.collections.RedisProperties;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

@Configuration
public class JedisConfig {

    private Logger logger = LoggerFactory.getLogger(JedisConfig.class);

    @Value("${my.redis.server.host}")
    private String host;

    @Value("${my.redis.server.port}")
    private int port;

    @Value("${my.redis.server.jedis.pool.maxTotal}")
    private int maxTotal;

    @Value("${my.redis.server.jedis.pool.maxIdle}")
    private int maxIdle;

    @Value("${my.redis.server.jedis.pool.maxWaitMillis}")
    private int maxWaitMillis;

    @Value("${my.redis.server.timeout}")
    private int timeout;

    @Bean(name = "jedisPool")
    public JedisPool jedisPool() {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(maxTotal);
        config.setMaxIdle(maxIdle);
        config.setMaxWaitMillis(maxWaitMillis);
        return new JedisPool(config, host, port, timeout);
    }

}
(2)密码
package com.demo.security.config;

import org.springframework.security.crypto.password.PasswordEncoder;

public class MyPasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence charSequence) {
        return charSequence.toString();
    }

    @Override
    public boolean matches(CharSequence charSequence, String s) {
        return s.equals(charSequence.toString());
    }

    /*public DefaultPasswordEncoder() {
        this(-1);
    }
    *//**
     * @param strength
     *            the log rounds to use, between 4 and 31
     *//*
    public DefaultPasswordEncoder(int strength) {
    }
    public String encode(CharSequence rawPassword) {
        return MD5.encrypt(rawPassword.toString());
    }
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));
    }*/
}
(3)security配置
package com.demo.security.config;

//import com.demo.security.filter.AuthenticationFilter;
//import com.demo.security.filter.LoginFilter;
import com.demo.security.filter.LoginFilter;
import com.demo.security.filter.TokenAuthenticationFilter;
import com.demo.security.util.RedisClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.authentication.configuration.GlobalAuthenticationConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;


import static io.netty.util.CharsetUtil.encoder;

@EnableWebSecurity
@Configuration
public class SecurityWebConfig {

    @Autowired
    private RedisClient redisClient;

    @Autowired
    private UserDetailsService userDetailsService;



    @Bean
    public PasswordEncoder passwordEncoder() {
        return new MyPasswordEncoder();
    }


    @Bean
    public AuthenticationManager authenticationManager(
            AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain configure(HttpSecurity http, AuthenticationManager authenticationManager) throws Exception {

        http.csrf(AbstractHttpConfigurer::disable);
       http.headers(AbstractHttpConfigurer::disable);

       http.sessionManagement(sessionManagement -> {
           sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
       });
       http.authorizeRequests().anyRequest().authenticated().and()

               //1、登陆、退出url,均由前端拦截器控制,这里注释掉。
               //1.1、前端拦截器中判断缓存token为空,为空则post请求访问/login,目的是进入LoginFilter获取token
               //1.2、不为空则带token访问接口,如果AuthenticationFilter拦截token不合法则根据错误码跳转到登陆页面,重复1.1的操作
               //.logout().logoutUrl("/logout").and()
               //2、身份认证filter,访问系统(除了白名单接口)需要先登陆。post请求/login接口会进入这个拦截器
               // 校验用户名密码是否正确,正确返回token给前端,不正确则返回异常信息
               .addFilterBefore(new LoginFilter(authenticationManager,redisClient), LoginFilter.class)
               //3、授权filer,authenticationManager为BasicAuthenticationFilter的必传参数。所有的接口都会走到这里
               // 根据用户id查询权限,连同身份一起塞入SecurityContextHolder全局变量,后面获取用户信息则直接从SecurityContextHolder中get
               .addFilterBefore(new TokenAuthenticationFilter(authenticationManager,redisClient,userDetailsService),TokenAuthenticationFilter.class);
       return http.build();
    }

 @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().requestMatchers("/param/**", "/ignore2");
    }

}
(4)WebMvcConfig
package com.demo.security.config;

import com.demo.security.filter.UrlTwoFilter;
import com.demo.security.interceptor.ParamInterceptor;
import com.demo.security.interceptor.ParamOneInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private ParamInterceptor paramInterceptor;

    @Autowired
    private ParamOneInterceptor paramOneInterceptor;


    @Autowired
    private UrlTwoFilter twoFilter;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(paramOneInterceptor);
        registry.addInterceptor(paramInterceptor).addPathPatterns("/**");
        //registry.addInterceptor(paramOneInterceptor);
    }

    @Bean
    public FilterRegistrationBean<UrlTwoFilter> getIpFilter() {
        FilterRegistrationBean<UrlTwoFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(twoFilter);
        registrationBean.setEnabled(true);
        registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
        registrationBean.setUrlPatterns(List.of("/*"));
        return registrationBean;
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().requestMatchers("/param/**", "/ignore2");
    }
}

5、常量与dto:
package com.demo.security.constant;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 模拟数据库查询数据,假设有:用户名/密码/角色/资源
 * admin/123/xtgly/user_manage、role_manage、menu_manage、school_manage
 * zs/123/userAdmin、roleAdmin/user_manage、role_manage、menu_manage
 * ls/123/schoolAdmin/school_manage
 */
public class UserConstants {

    //redis缓存过期时间
    public static final int EXPIRE_TIME = 30 * 60 * 1000;

    public static Map<String, String> getUsers() {
        Map<String,String> users = new HashMap<>();
        users.put("admin","123");
        users.put("zs","123");
        users.put("ls","123");
        return users;
    }

    public static Map<String,List<String>> getUserRoles() {
        Map<String,List<String>> userRoles = new HashMap<>();
        //admin
        List<String> adminRoles = new ArrayList<>();
        adminRoles.add("xtgly");
        userRoles.put("admin",adminRoles);
        //zs
        List<String> zsRoles = new ArrayList<>();
        zsRoles.add("userAdmin");
        zsRoles.add("roleAdmin");
        userRoles.put("zs",zsRoles);
        //ls
        List<String> lsRoles = new ArrayList<>();
        lsRoles.add("schoolAdmin");
        userRoles.put("ls",lsRoles);
        return userRoles;
    }

    public static Map<String,List<String>> getUserPermissions() {
        Map<String,List<String>> userPermissions = new HashMap<>();
        List<String> lsPermissions = new ArrayList<>();
        //ls
        lsPermissions.add("school_manage");
        userPermissions.put("ls",lsPermissions);
        //zs
        List<String> zsPermissions = new ArrayList<>();
        zsPermissions.add("user_manage");
        zsPermissions.add("role_manage");
        zsPermissions.add("menu_manage");
        userPermissions.put("zs",zsPermissions);
        //admin
        List<String> adminPermissions = new ArrayList<>();
        adminPermissions.add("user_manage");
        adminPermissions.add("role_manage");
        adminPermissions.add("menu_manage");
        adminPermissions.add("school_manage");
        userPermissions.put("admin",adminPermissions);
        return userPermissions;
    }
}
package com.demo.security.dto;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;

public class UserDTO implements UserDetails {

    private Integer id;
    private String userName;
    private String userAccount;
    private List<String> roles;
    private List<String> menus;
    private String passWord;

    public UserDTO (Integer id,String userName,String userAccount,List<String> roles,List<String> menus,String passWord){
        this.id = id;
        this.userAccount = userAccount;
        this.userName = userName;
        this.roles = roles;
        this.menus = menus;
        this.passWord = passWord;
    }

    public List<String> getMenus() {
        return menus;
    }

    public void setMenus(List<String> menus) {
        this.menus = menus;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return passWord;
    }

    @Override
    public String getUsername() {
        return this.userAccount;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}
6、过滤器&拦截器
(1)security自带的过滤器链
package com.demo.security.filter;

import com.demo.security.constant.UserConstants;
import com.demo.security.dto.UserDTO;
import com.demo.security.util.JwtUtil;
import com.demo.security.util.RedisClient;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.http.entity.ContentType;
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.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.StringUtils;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;

public class LoginFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;
    private RedisClient redisClient;

    public LoginFilter(AuthenticationManager authenticationManager, RedisClient redisClient) {
        //super(new AntPathRequestMatcher("/login", "POST"));
        this.authenticationManager = authenticationManager;
        this.redisClient = redisClient;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException {
        try {
            String userName = req.getParameter("userName");
            String passWord = req.getParameter("passWord");
            if (StringUtils.isEmpty(userName)) {
                throw new UsernameNotFoundException("请输入账号");
            }
            if (StringUtils.isEmpty(passWord)) {
                throw new UsernameNotFoundException("请输入密码");
            }
            //验证用户名密码是否正确
            Map<String, String> userMap = UserConstants.getUsers();
            if(!userMap.keySet().contains(userName)){
                throw new UsernameNotFoundException("用户不存在");
            }
            if(!passWord.equals(userMap.get(userName))){
                throw new UsernameNotFoundException("密码错误");
            }
            //这里权限返回空,由后面的授权过滤器查询
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(userName, passWord, new ArrayList<>()));
        } catch (UsernameNotFoundException e) {
            //返回错误信息
            res.setCharacterEncoding("UTF-8");
            res.setContentType("application/text;charset=utf-8");
            try {
                res.getWriter().write(e.getMessage());
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            return null;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }


    @Override
    protected void successfulAuthentication(
            HttpServletRequest request,
            HttpServletResponse res,
            jakarta.servlet.FilterChain chain,
            Authentication authResult)
            throws IOException, jakarta.servlet.ServletException {
        UserDTO userDTO = (UserDTO) authResult.getPrincipal();
        String userName = userDTO.getUsername();
        String password = userDTO.getPassword();
        String jwtToken = JwtUtil.sign(userName, password);
        //缓存到redis中
        redisClient.set(userName,jwtToken);
        //返回
        res.setContentType(ContentType.TEXT_HTML.toString());
        res.getWriter().write(jwtToken);
    }
}
package com.demo.security.filter;

import com.demo.security.util.JwtUtil;
import com.demo.security.util.RedisClient;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;

import java.io.IOException;
@Slf4j
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {
    private RedisClient redisClient;

    private UserDetailsService userDetailsService;

    public TokenAuthenticationFilter(AuthenticationManager authenticationManager, RedisClient redisClient, UserDetailsService userDetailsService) {
        super(authenticationManager);
        this.redisClient = redisClient;
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
        logger.info("登陆成功后访问,url={}"+req.getRequestURI());
        String token = req.getHeader("token");
        res.setCharacterEncoding("UTF-8");
        res.setContentType("application/text;charset=utf-8");
        if(StringUtils.isEmpty(token)){
            logger.info("登陆成功后访问,url={},token为空"+req.getRequestURI());
            res.getWriter().write("token为空");
            return;
        }
        //1、token是否正确,是否能反解析出userName
        String userName = JwtUtil.getUsername(token);
        if(StringUtils.isEmpty(userName)){
            logger.info("登陆成功后访问,url={},token错误"+req.getRequestURI());
            res.getWriter().write("token错误");
            return;
        }
        //2、验证token是否过期
        String redisToken = redisClient.get(userName);
        if(StringUtils.isEmpty(redisToken) || !token.equals(redisToken)){
            logger.info("登陆成功后访问,url={},token过期"+req.getRequestURI());
            res.getWriter().write("token过期");
            return;
        }
        UserDetails currentUser = userDetailsService.loadUserByUsername(userName);
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(currentUser,null,currentUser.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(req, res);
    }
}
(2)自定义的过滤器: 
package com.demo.security.filter;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Component
@Slf4j
//所有filter中优先级最高
@Order(Ordered.HIGHEST_PRECEDENCE+1)
public class UrlFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, jakarta.servlet.FilterChain filterChain) throws jakarta.servlet.ServletException, IOException {
        log.info("进入UrlFilter");
        filterChain.doFilter(request,response);
    }
}
package com.demo.security.filter;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Component
@Slf4j
//@Order(1)
@Order(Ordered.HIGHEST_PRECEDENCE+1)
public class UrlOneFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, jakarta.servlet.FilterChain filterChain) throws jakarta.servlet.ServletException, IOException {
        log.info("进入UrlFilter-one");
        filterChain.doFilter(request,response);
    }
}

package com.demo.security.filter;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Component
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE+1)
public class UrlTwoFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, jakarta.servlet.FilterChain filterChain) throws jakarta.servlet.ServletException, IOException {
        log.info("进入UrlFilter-two");
        filterChain.doFilter(request,response);
    }
}
(3)自定义的拦截器 
package com.demo.security.interceptor;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
@Slf4j
public class ParamInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("进入ParamInterceptor");
        return true;
    }
}
package com.demo.security.interceptor;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
@Slf4j
public class ParamOneInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("进入ParamInterceptor-one");
        return true;
    }
}

7、service
package com.demo.security.service;


import com.demo.security.constant.UserConstants;
import com.demo.security.dto.UserDTO;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Map;

/**
 * 当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的。
 * 而在实际项目中账号和密码都是从数据库中查询出来的。所以我们要通过自定义逻辑控制认证逻辑,
 */
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //模拟数据库查询
        Map<String, String> userMap = UserConstants.getUsers();
        String dbPwd = userMap.get(username);
        if(StringUtils.isEmpty(dbPwd)){
            throw new UsernameNotFoundException("用户不存在");
        }
        Map<String, List<String>> userRoles = UserConstants.getUserRoles();
        List<String> roles = userRoles.get(username);
        Map<String, List<String>> userMenus = UserConstants.getUserPermissions();
        List<String> menus = userMenus.get(username);
        UserDTO userDTO = new UserDTO(null,null,username,roles,menus,dbPwd);
        return userDTO;
    }
}
8、util
package com.demo.security.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.demo.security.constant.UserConstants;


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

public class JwtUtil {

    /**
     * 校验token是否正确
     * @param token  密钥
     * @param secret 用户的密码
     * @return 是否正确
     */
    public static boolean verify(String token, String username, String secret) {
        try {
            // 根据密码生成JWT效验器
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
            // 效验TOKEN
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 获得token中的信息无需secret解密也能获得
     * @return token中包含的用户名
     */
    public static String getUsername( HttpServletRequest req) {
        try {
            String token =  req.getHeader("token");
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    public static String getUsername( String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /**
     * 生成签名,5min(分钟)后过期
     * @param username 用户名
     * @param secret   用户的密码
     * @return 加密的token
     */
    public static String sign(String username, String secret) {
        Date date = new Date(System.currentTimeMillis() + UserConstants.EXPIRE_TIME);
        Algorithm algorithm = Algorithm.HMAC256(secret);
        // 附带username信息
        return JWT.create()
                .withClaim("username", username)
                .withExpiresAt(date)
                .sign(algorithm);
    }
}
package com.demo.security.util;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;



@SuppressWarnings("unused")
@Component
public class RedisClient {
    private static boolean BORROW = true; // 在borrow一个事例时是否提前进行validate操作
    private static Logger logger = LoggerFactory.getLogger(RedisClient.class);
    @Autowired
    private JedisPool pool;


    /**
     * 获取连接
     */
    public  synchronized Jedis getJedis() {
        try {
            if (pool != null) {
                return pool.getResource();
            } else {
                return null;
            }
        } catch (Exception e) {
            logger.info("连接池连接异常");
            return null;
        }

    }




    /**
     * @Description: 关闭连接
     * @param @param jedis
     * @return void 返回类型
     */
    public static void getColse(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }


    /**
     * 格式化Key
     */
    public static String format(String formatKey, String... keyValues) {
        if (keyValues == null || keyValues.length == 0) {
            return formatKey;
        }
        StringBuilder key = new StringBuilder();
        char[] chars = formatKey.toCharArray();
        int index = -1;
        boolean inmark = false;
        boolean firstinmark = false;
        for (int i = 0; i < chars.length; i++) {
            char ch = chars[i];
            if (ch == '{') {
                index++;
                inmark = true;
                firstinmark = true;
            } else if (ch == '}') {
                inmark = false;
            } else if (inmark) {
                if (firstinmark) {
                    firstinmark = false;
                    key.append(keyValues[index]);
                }
            } else {
                key.append(chars[i]);
            }
        }
        return key.toString();
    }

    /********************************** 针对key的操作 **************************************/

    /**
     * 删除一个key
     *
     * @param keyFormat
     *            key标识
     * @param keyValues
     *            key变量
     * @return 被删除的keys的数量
     */
    public Long del(String keyFormat, String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.del(key);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 查询一个key是否存在
     *
     * @param keyFormat
     *            key标识
     * @param keyValues
     *            key变量
     * @return key是否存在。
     */
    public boolean exists(String keyFormat, String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.exists(key);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 设置一个key的过期的秒数
     *
     * @param keyFormat
     *            key标识
     * @param seconds
     *            过期的秒数
     * @param keyValues
     *            key变量
     * @return 1表示设置成功, 0 表示设置失败或者无法被设置
     */
    public Long expire(String keyFormat, int seconds, String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.expire(key, seconds);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 设置一个UNIX时间戳的过期时间
     *
     * @param keyFormat
     *            key标识
     * @param expireDate
     *            过期时间
     * @param keyValues
     *            key变量
     * @return 1表示设置成功, 0 表示设置失败或者无法被设置
     */
    public Long expireAt(String keyFormat, Date expireDate, String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.pexpireAt(key, expireDate.getTime());
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 移除给定key的生存时间,将这个 key 从『易失的』(带生存时间 key )转换成『持久的』(一个不带生存时间、永不过期的 key )。
     *
     * @param keyFormat
     *            key标识
     * @param keyValues
     *            key变量
     * @return 当生存时间移除成功时,返回 1 . 如果 key 不存在或 key 没有设置生存时间,返回 0 .
     */
    public Long persist(String keyFormat, String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.persist(key);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 设置一个key的过期的毫秒数
     *
     * <pre>
     * 这个命令和 EXPIRE 命令的作用类似,但是它以毫秒为单位设置 key 的生存时间,而不像 EXPIRE 命令那样,以秒为单位。
     * </pre>
     *
     * @param keyFormat
     *            key标识
     * @param milliSeconds
     *            过期的毫秒数
     * @param keyValues
     *            key变量
     * @return 设置成功,返回 1,不存在或设置失败,返回 0
     */
    public Long pexpire(String keyFormat, long milliSeconds,
                        String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.pexpire(key, milliSeconds);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

  

    /**
     * 整数原子减1
     *
     * <pre>
     * 对key对应的数字做减1操作。如果key不存在,那么在操作之前,这个key对应的值会被置为0。
     * 如果key有一个错误类型的value或者是一个不能表示成数字的字符串,就返回错误。
     * 这个操作最大支持在64位有符号的整型数字。
     * </pre>
     *
     * @param keyFormat
     *            key标识
     * @param keyValues
     *            key变量
     * @return 数字:减小之后的value
     */
    public Long decr(String keyFormat, String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.decr(key);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

  

    /**
     * 设置一个key的value,并获取设置前的值
     *
     * <pre>
     * 自动将key对应到value并且返回原来key对应的value。如果key存在但是对应的value不是字符串,就返回错误。
     * exp:
     * GETSET可以和INCR一起使用实现支持重置的计数功能。
     * 举个例子:每当有事件发生的时候,一段程序都会调用INCR给key mycounter加1,但是有时我们需要获取计数器的值,并且自动将其重置为0。
     * 这可以通过GETSET mycounter "0"来实现:
     * </pre>
     *
     * @param keyFormat
     *            key标识
     * @param value
     *            要设置的值
     * @param keyValues
     *            key变量
     * @return 设置之前的值
     */
    public String getSet(String keyFormat, String value, String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.getSet(key, value);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 执行原子加1操作
     *
     * <pre>
     * 对key对应的数字做加1操作。如果key不存在,那么在操作之前,这个key对应的值会被置为0。
     * 如果key有一个错误类型的value或者是一个不能表示成数字的字符串,就返回错误。这个操作最大支持在64位有符号的整型数字。
     * 提醒:这是一个string操作,因为Redis没有专用的数字类型。key对应的string都被解释成10进制64位有符号的整型来执行这个操作。
     * Redis会用相应的整数表示方法存储整数,所以对于表示数字的字符串,没必要为了用字符串表示整型存储做额外开销。
     * </pre>
     *
     * @param keyFormat
     *            key标识
     * @param keyValues
     *            key变量
     * @return 增加之后的value
     */
    public Long incr(String keyFormat, String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.incr(key);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 执行原子加1操作,并且设置过期时间(单位:s)
     *
     * <pre>
     * 本操作是在{@linkplain RedisClient#incr(String, String...)}之上增加了一个设置过期时间的操作
     * </pre>
     *
     * @param keyFormat
     *            key标识
     * @param expireTime
     *            过期时间(单位:s)
     * @param keyValues
     *            key变量
     * @return 增加之后的value
     */
    public Long incr(String keyFormat, int expireTime, String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            long result = jedis.incr(key);
            jedis.expire(key, expireTime);
            return result;
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 执行原子增加一个整数
     *
     * <pre>
     * 将key对应的数字加increment。如果key不存在,操作之前,key就会被置为0。
     * 如果key的value类型错误或者是个不能表示成数字的字符串,就返回错误。这个操作最多支持64位有符号的正型数字。
     * 查看方法{@linkplain RedisClient#incr(String, String...)}了解关于增减操作的额外信息。
     * </pre>
     *
     * @param keyFormat
     *            key标识
     * @param increment
     *            要增加的数值
     * @param keyValues
     *            key变量
     * @return 增加后的value
     */
    public Long incrBy(String keyFormat, long increment, String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.incrBy(key, increment);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 执行原子增加一个浮点数
     *
     * <pre>
     * 将key对应的数字加increment。如果key不存在,操作之前,key就会被置为0。
     * 如果key的value类型错误或者是个不能表示成数字的字符串,就返回错误。
     * </pre>
     *
     * @param keyFormat
     *            key标识
     * @param increment
     *            要增加的数值
     * @param keyValues
     *            key变量
     * @return 增加后的value
     */
    public Double incrByFloat(String keyFormat, double increment,
                              String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.incrByFloat(key, increment);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 设置一个key的value值
     *
     * <pre>
     * 警告:如果key已经存在了,它会被覆盖,而不管它是什么类型。
     * </pre>
     *
     * @param keyFormat
     *            key标识
     * @param value
     *            要设置的值
     * @param keyValues
     *            key变量
     *
     * @return 总是"OK"
     */
    public String set(String keyFormat, String value, String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.set(key, value);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 设置key-value并设置过期时间(单位:秒)
     *
     * <pre>
     * 设置key对应字符串value,并且设置key在给定的seconds时间之后超时过期。
     * 该命令相当于执行了{@link #set(String, String, String...) SET} + {@link #expire(String, int, String...) EXPIRE}.并且该操作是原子的
     * </pre>
     *
     * @param keyFormat
     *            key标识
     * @param seconds
     *            超时时间(单位:s)
     * @param value
     *            设置的值
     * @param keyValues
     *            key变量
     * @return 状态码
     */
    public String setex(String keyFormat, int seconds, String value,
                        String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.setex(key, seconds, value);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 设置key-value并设置过期时间(单位:毫秒)
     *
     * <pre>
     * 跟{@link #setex(String, int, String, String...)}效果差不多,唯一区别是超时时间是ms
     * </pre>
     *
     * @param keyFormat
     *            key标识
     * @param milliseconds
     *            超时时间(单位:ms)
     * @param value
     *            设置的值
     * @param keyValues
     *            key变量
     * @return 状态码
     */
    public String psetex(String keyFormat, int milliseconds, String value,
                         String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.psetex(key, (long) milliseconds, value);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 设置的一个关键的价值,只有当该键不存在
     *
     * <pre>
     * 如果key不存在,就设置key对应字符串value。在这种情况下,该命令和SET一样。
     * 当key已经存在时,就不做任何操作。SETNX是"SET if Not eXists"。
     * </pre>
     *
     * @param keyFormat
     *            key标识
     * @param value
     *            设置的value
     * @param keyValues
     *            key变量
     * @return 1 如果key被set,0 如果key没有被set
     */
    public Long setnx(String keyFormat, String value, String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.setnx(key, value);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /********************************* Hash 操作 ******************************/

    /**
     * 删除一个哈希域
     *
     * <pre>
     * 从 key 指定的哈希集中移除指定的域。在哈希集中不存在的域将被忽略。
     * 如果 key 指定的哈希集不存在,它将被认为是一个空的哈希集,该命令将返回0。
     * </pre>
     *
     * @param keyFormat
     *            key标识
     * @param field
     *            要删除的域
     * @param keyValues
     *            key变量
     * @return 返回从哈希集中成功移除的域的数量,不包括指出但不存在的那些域
     */
    public long hdel(String keyFormat, String field, String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hdel(key, field);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 删除一个hash中的多个域
     *
     * <pre>
     * 相当于多次执行{@link #hdel(String, String, String...)}, 效率会有所提高
     * </pre>
     *
     * @param keyFormat
     *            key标识
     * @param fileds
     *            要删除的域
     * @param keyValues
     *            key变量
     *
     * @return 返回从哈希集中成功移除的域的数量,不包括指出但不存在的那些域
     */
    public Long hdel(String keyFormat, Set<String> fileds, String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hdel(key, fileds.toArray(new String[fileds.size()]));
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 判断给定域是否存在于哈希集中
     *
     * @param keyFormat
     *            key标识
     * @param field
     *            指定的域
     * @param keyValues
     *            key变量
     * @return 返回字段是否是 key 指定的哈希集中存在的字段。
     */
    public Boolean hexists(String keyFormat, String field, String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hexists(key, field);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 读取哈希域的的值
     *
     * @param keyFormat
     *            key标识
     * @param field
     *            指定的域
     * @param keyValues
     *            key变量
     * @return 该字段所关联的值。当字段不存在或者 key 不存在时返回null。
     */
    public String hget(String keyFormat, String field, String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hget(key, field);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 读取哈希域的的值(返回的是byte[])
     *
     * @param keyFormat
     *            key标识
     * @param field
     *            指定的域
     * @param keyValues
     *            key变量
     * @return 该字段所关联的值(byte[])。当字段不存在或者 key 不存在时返回null。
     */
    public byte[] hgetBytes(String keyFormat, String field, String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hget(key.getBytes(), field.getBytes());
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 从哈希集中读取全部的域和值
     *
     * @param keyFormat
     *            key标识
     * @param keyValues
     *            key变量
     * @return 哈希集中字段和值的列表。当 key 指定的哈希集不存在时返回空列表。
     */
    public Map<String, String> hgetAll(String keyFormat, String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hgetAll(key);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 将哈希集中指定域的值增加给定的数字
     *
     * <pre>
     * 增加 key 指定的哈希集中指定字段的数值。
     * 如果 key 不存在,会创建一个新的哈希集并与 key 关联。
     * 如果字段不存在,则字段的值在该操作执行前被设置为 0
     * HINCRBY 支持的值的范围限定在 64位 有符号整数
     * </pre>
     *
     * @param keyFormat
     *            key标识
     * @param field
     *            指定的域
     * @param value
     *            给定的数字
     * @param keyValues
     *            key变量
     * @return 增值操作执行后的该字段的值。
     */
    public long hincrBy(String keyFormat, String field, long value,
                        String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hincrBy(key, field, value);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 将哈希集中指定域的值增加给定的浮点数
     *
     * @param keyFormat
     *            key标识
     * @param field
     *            指定的域
     * @param value
     *            给定的浮点数
     * @param keyValues
     *            key变量
     * @return 增值操作执行后的该字段的值。
     */
    public Double hincrByFloat(String keyFormat, String field, double value,
                               String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hincrByFloat(key, field, value);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 获取hash的所有字段
     *
     * @param keyFormat
     *            key标识
     * @param keyValues
     *            key变量
     * @return 哈希集中的字段列表,当 key 指定的哈希集不存在时返回空列表。
     */
    public Set<String> hkeys(String keyFormat, String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hkeys(key);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 获取hash里所有字段的数量
     *
     * @param keyFormat
     *            key标识
     * @param keyValues
     *            key变量
     * @return 哈希集中字段的数量,当 key 指定的哈希集不存在时返回 0
     */
    public Long hlen(String keyFormat, String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hlen(key);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 批量获取hash中的值
     *
     * <pre>
     * 返回 key 指定的哈希集中指定字段的值。
     * 对于哈希集中不存在的每个字段,返回null值。
     * 因为不存在的keys被认为是一个空的哈希集,对一个不存在的 key 执行 HMGET 将返回一个只含有 null值的列表
     * </pre>
     *
     * @param keyFormat
     *            key标识
     * @param fileds
     *            指定的域
     * @param keyValues
     *            key变量
     * @return 含有给定字段及其值的列表,并保持与请求相同的顺序。
     */
    public List<String> hmget(String keyFormat, List<String> fileds,
                              String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hmget(key, fileds.toArray(new String[fileds.size()]));
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 批量设置hash中的值
     *
     * <pre>
     * 设置 key 指定的哈希集中指定字段的值。该命令将重写所有在哈希集中存在的字段。
     * 如果 key 指定的哈希集不存在,会创建一个新的哈希集并与 key 关联
     * </pre>
     *
     * @param keyFormat
     *            key标识
     * @param hash
     *            指定的域和值
     * @param keyValues
     *            key变量
     * @return Return OK or Exception if hash is empty
     */
    public String hmset(String keyFormat, Map<String, String> hash,
                        String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hmset(key, hash);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 设置hash里面一个字段的值
     *
     * @param keyFormat
     *            key标识
     * @param field
     *            字段
     * @param value
     *            值
     * @param keyValues
     *            key变量
     * @return 含义如下:1如果field是一个新的字段 0如果field原来在map里面已经存在
     *
     */
    public Long hset(String keyFormat, String field, String value,
                     String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hset(key, field, value);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 设置hash里面一个字段的值(值为byte[]数组类型)
     *
     * @param keyFormat
     *            key标识
     * @param field
     *            字段
     * @param value
     *            值
     * @param keyValues
     *            key变量
     * @return 含义如下:1如果field是一个新的字段 0如果field原来在map里面已经存在
     *
     */
    public Long hset(String keyFormat, String field, byte[] value,
                     String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hset(key.getBytes(), field.getBytes(), value);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 设置hash的一个字段,只有当这个字段不存在时有效
     *
     * <pre>
     * 只在 key 指定的哈希集中不存在指定的字段时,设置字段的值。
     * 如果 key 指定的哈希集不存在,会创建一个新的哈希集并与 key 关联。
     * 如果字段已存在,该操作无效果。
     * </pre>
     *
     * @param keyFormat
     *            key标识
     * @param field
     *            字段
     * @param value
     *            值
     * @param keyValues
     *            key变量
     * @return 1:如果字段是个新的字段,并成功赋值 0:如果哈希集中已存在该字段,没有操作被执行
     */
    public Long hsetnx(String keyFormat, String field, String value,
                       String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hsetnx(key, field, value);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 获得hash的所有值
     *
     * @param keyFormat
     *            key标识
     * @param keyValues
     *            key变量
     * @return 哈希集中的值的列表,当 key 指定的哈希集不存在时返回空列表。
     */
    public List<String> hvals(String keyFormat, String... keyValues) {
        String key = format(keyFormat, keyValues);
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hvals(key);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }
  
}
9、controller
package com.demo.security.controller;


import com.demo.security.dto.UserDTO;
import com.demo.security.util.RedisClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;


@RequestMapping("/user")
@RestController
public class UserController {

    @Autowired
    private RedisClient redisClient;

    @RequestMapping("/test")
    public String test(){
        return "这是user test";
    }

    @RequestMapping("/getCurrentUser")
    public UserDTO getCurrentUser() {
        UserDTO currentUser = (UserDTO) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        System.out.println("当前用户为:"+currentUser);
        return currentUser;
    }

    //退出登陆
    @RequestMapping("/logOut")
    public void logOut(){
        UserDTO currentUser = (UserDTO) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        String userAccount = currentUser.getUsername();
        redisClient.del(userAccount);
    }
}


package com.demo.security.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/school")
@RestController
public class SchoolManageController {
    @RequestMapping("/test")
    public String test(){
        return "这是学校管理";
    }
}
package com.demo.security.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/param")
@RestController
public class ParamController {

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

10、测试:
10.1、认证与拦截

(1)登录localhost:2222/securityDemo/login?userName=ls&passWord=123

(2)访问其他接口

localhost:2222/securityDemo/user/getCurrentUser

(3)退出登录

localhost:2222/securityDemo/user/logOut

(4)再次访问其他接口

均提示登录过期

(5)白名单

访问localhost:2222/securityDemo/param/test,没有带token也可以返回结果

后台打印

2024-04-29T17:34:07.714+08:00  INFO 14256 --- [nio-2222-exec-9] com.demo.security.filter.UrlTwoFilter    : 进入UrlFilter-two
2024-04-29T17:34:07.715+08:00  INFO 14256 --- [nio-2222-exec-9] com.demo.security.filter.UrlFilter       : 进入UrlFilter
2024-04-29T17:34:07.715+08:00  INFO 14256 --- [nio-2222-exec-9] com.demo.security.filter.UrlOneFilter    : 进入UrlFilter-one
2024-04-29T17:34:07.715+08:00  INFO 14256 --- [nio-2222-exec-9] c.d.s.interceptor.ParamOneInterceptor    : 进入ParamInterceptor-one
2024-04-29T17:34:07.715+08:00  INFO 14256 --- [nio-2222-exec-9] c.d.s.interceptor.ParamInterceptor       : 进入ParamInterceptor

二、校验

这里采用注解式校验:

1、自定义校验方法
package com.demo.security.check;

import com.demo.security.dto.UserDTO;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import java.awt.*;
import java.util.List;

@Component("menuAuthorizationCheck")
public class MenuAuthorizationCheck {

    public boolean hasMenuAuthorization(String menuCode) {
        UserDTO currentUser = (UserDTO) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        List<String> menus = currentUser.getMenus();
        return menus.contains(menuCode);
    }
}
2、接口添加权限校验
@RequestMapping("/menu")
@RestController
public class MenuManageController {

    @PreAuthorize("@menuAuthorizationCheck.hasMenuAuthorization('menu_manage')")
    @RequestMapping("/test")
    public String test(){
        return "这是菜单管理";
    }
}
3、测试:

无权限用户访问,无内容返回:

有权限用户访问,返回内容:

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

w_t_y_y

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值