一、认证&授权&拦截:
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、测试:
无权限用户访问,无内容返回:
有权限用户访问,返回内容: