最近搭建了一个后台管理系统,基于要求使用的是:Springboot+mybatis+mysql+security+jwt;
security : 作为权限控制框架,可以根据不同的角色控制不同的权限请求;
jwt :作为token的管理器,生成,校验token;
1. 创建springboot 项目
1.采用网页的方式创建,(圈住的网址);然后将生成的项目导入本地即可;
2.自己创建 (本人)

2.导入依赖
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.security</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</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-data-redis</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.配置文件
注意:ignored.urls=忽略校验token的路径
server:
port: 8085
servlet:
context-path: /
tomcat:
uri-encoding: UTF-8
max-threads: 1000
min-spare-threads: 30
spring:
# 数据源
datasource:
url: jdbc:log4jdbc:mysql://127.0.0.1:3306/cdm?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
# Druid StatViewServlet配置
druid:
stat-view-servlet:
# 默认true 内置监控页面首页/druid/index.html
enabled: true
url-pattern: /druid/*
# 允许清空统计数据
reset-enable: true
login-username: oa-cisdom
login-password: oa-cisdom
# IP白名单 多个逗号分隔
allow:
# IP黑名单
deny:
jackson:
serialization:
WRITE_DATES_AS_TIMESTAMPS: false
jpa:
show-sql: true
# 自动生成表结构
generate-ddl: false
open-in-view: false
hibernate:
ddl-auto: none
# 忽略鉴权url
ignored:
urls:
- /druid/**
- /swagger-ui.html
- /swagger-ui.html/**
- /swagger-resources/**
- /webjars/springfox-swagger-ui/**
- /swagger/**
- /v2/**
- /**/*.js
- /**/*.css
- /**/*.png
- /**/*.ico
- /error
- /auth/login
- /mapper/*
- /name/list
#jwt
jwt:
header: Authorization
secret: oa-cisdom
# token 过期时间 2个小时
expiration: 7200000
auth:
# 授权路径
path: /login
# 获取用户信息
account: /info
4. token工具类 。主要作用是生成token,校验token,获取token中一些信息等;
package com.tools.toolmange.security.utils;
import com.tools.toolmange.security.security.JwtUser;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Clock;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.DefaultClock;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtTokenUtil implements Serializable {
private static final long serialVersionUID = -8898873260896775007L;
private Clock clock = DefaultClock.INSTANCE;
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
public String getUsernameFromToken(String token) throws Exception {
return getClaimFromToken(token, Claims::getSubject);
}
public String getAudienceFromToken(String token) throws Exception {
return getClaimFromToken(token, Claims::getAudience);
}
public Date getIssuedAtDateFromToken(String token) throws Exception {
return getClaimFromToken(token, Claims::getIssuedAt);
}
public Date getExpirationDateFromToken(String token) throws Exception {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) throws Exception {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) throws Exception {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
public String generateToken(JwtUser userDetails) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, userDetails);
}
private String doGenerateToken(Map<String, Object> claims, JwtUser userDetails) {
final Date createdDate = clock.now();
final Date expirationDate = calculateExpirationDate(createdDate);
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setIssuedAt(createdDate)
.setAudience(userDetails.getRemoteIp())
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
//防止token过期 7天token失效 服务器不连续一小时操作即刻失效
private Date calculateExpirationDate(Date createdDate) {
return new Date(createdDate.getTime() + 604800000);
}
public boolean validateToken(String authToken, JwtUser userDetails) throws Exception {
final Date created = getIssuedAtDateFromToken(authToken);
return (!isTokenExpired(authToken)
&& !isCreatedBeforeUpdatedReset(created, userDetails.getCreateTime())
);
}
private Boolean isTokenExpired(String token) throws Exception {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(clock.now());
}
private boolean isCreatedBeforeUpdatedReset(Date created, Date updateTime) {
return (updateTime != null && created.before(updateTime));
}
}
5. 忽略路径读取类,配置拦截器,验证类
读取配置文件中设置的那些忽略路径,加载到缓存中,方便后面使用;此处配置主要是配置跨域及静态资源的过滤
package com.tools.toolmange.common.ignore;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Data
@Configuration
@Component
@ConfigurationProperties(prefix = "ignored")
public class IgnoredUrlsProperties {
private List<String> urls = Arrays.asList("/resources/)");
}
校验拦截器
package com.tools.toolmange.common.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/*
* @功能描述:校驗接口
* @作者:lr
* @时间:2019/8/6
* @备注:
*/
@Slf4j
@Component
public class AuthorizationInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
//打印请求参数
return super.preHandle(request, response, handler);
}
}
配置拦截器,设置拦截的路径及忽略的路径 (捎带着解决跨域问题)
package com.tools.toolmange.common.interceptor;
import com.tools.toolmange.common.ignore.IgnoredUrlsProperties;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;
import javax.annotation.Resource;
@Configuration
@EnableWebMvc
@Log4j2
public class InterceptorConfiguration implements WebMvcConfigurer {
@Value("${image_url}")
private String imageUrl;
@Resource
private AuthorizationInterceptor authorizationInterceptor;
@Autowired
private IgnoredUrlsProperties ignoredUrlsProperties;
//解决跨域问题配置
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(true)
.allowedHeaders("*")
.allowedOrigins("*")
.allowedMethods("GET","POST","PUT","DELETE");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/resources/",imageUrl).setCachePeriod(0);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器
InterceptorRegistration ir = registry.addInterceptor(authorizationInterceptor);
// 配置拦截的路径
ir.addPathPatterns("/**");
// 配置不拦截的路径
ir.excludePathPatterns(ignoredUrlsProperties.getUrls());
}
}
5. SecurityContextHolder,获取用户登录的缓存信息,缓存在security的缓存中,具体可参见security缓存策略介绍,方便以后获取用户信息使用;
package com.tools.toolmange.common.contextholder;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.userdetails.UserDetails;
public class SecurityContextHolder {
public static UserDetails getUserDetails() {
UserDetails userDetails = null;
try {
userDetails = (UserDetails) org.springframework.security.core.context.SecurityContextHolder.getContext().getAuthentication().getPrincipal();
} catch (Exception e) {
throw new BadCredentialsException("登录状态过期");
}
return userDetails;
}
}
6. security核心配置
package com.tools.toolmange.security.config;
import com.tools.toolmange.common.ignore.IgnoredUrlsProperties;
import com.tools.toolmange.security.security.JwtAccessDeniedHandler;
import com.tools.toolmange.security.security.JwtAuthenticationEntryPoint;
import com.tools.toolmange.security.security.JwtAuthorizationTokenFilter;
import com.tools.toolmange.security.security.JwtLogoutHandler;
import com.tools.toolmange.security.service.JwtUserDetailsService;
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.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.StrictHttpFirewall;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.annotation.Resource;
/*
* @功能描述:配置全局校验逻辑 拦截器拦截请求
* @作者:lr
* @时间:2019/8/6
* @备注:
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private IgnoredUrlsProperties ignoredUrlsProperties;
@Resource
private JwtAuthenticationEntryPoint authenticationEntryPoint;
@Resource
private JwtAccessDeniedHandler authAccessDeniedHandler;
@Autowired
private JwtAuthorizationTokenFilter authenticationRequestFilter;
@Autowired
private JwtUserDetailsService userDetailsService;
@Autowired
private JwtLogoutHandler jwtLogoutHandler;
@Value("${jwt.auth.path}")
private String loginPath;
@Bean
public PasswordEncoder passwordEncoderBean() {
return new BCryptPasswordEncoder();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoderBean());
}
@Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
// Remove the ROLE_ prefix 角色无前缀
return new GrantedAuthorityDefaults("");
}
@Bean
public HttpFirewall allowUrlEncodedSlashHttpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowUrlEncodedSlash(true);
return firewall;
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
//忽略路径
for (String url : ignoredUrlsProperties.getUrls()) {
httpSecurity.authorizeRequests().antMatchers(url).permitAll();
}
httpSecurity.csrf().disable()
.httpBasic().authenticationEntryPoint(authenticationEntryPoint)
//session状态 无状态
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
//请求之前的认证过滤器 token校验
.and().authorizeRequests()
.antMatchers("/jpa/**").permitAll()
// 所有请求都需要认证
.anyRequest().authenticated().and().cors();
httpSecurity
.headers()
.frameOptions().sameOrigin() // required to set for H2 else H2 Console will be blank.
.cacheControl();
//httpSecurity.authorizeRequests().requestMatchers(CorsUtils::isPreFlightRequest).permitAll();
httpSecurity.exceptionHandling().accessDeniedHandler(authAccessDeniedHandler);
httpSecurity.addFilterBefore(authenticationRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
web.httpFirewall(allowUrlEncodedSlashHttpFirewall());
}
}
7.JwtAccessDeniedHandler,JwtAuthenticationEntryPoint
package com.tools.toolmange.security.security;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler, Serializable {
private static final long serialVersionUID = -1846494756214482215L;
@Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException authException) throws IOException, ServletException {
/**
* 用户已经通过了认证,在访问一个受保护的资源,但是权限不够,那么抛出异常
*/
response.sendError(605, authException==null?"Unauthorized":authException.getMessage());
}
}
package com.tools.toolmange.security.security;
import lombok.extern.log4j.Log4j2;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;
@Component
@Log4j2
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
log.error(authException == null ? "Unauthorized" : authException.getMessage());
//当用户请求了一个受保护的资源,但是用户没有通过认证,那么抛出异常
response.sendError(403, authException == null ? "Unauthorized" :authException.getMessage());
}
}
8. jwtUser UserDetails的子类
package com.tools.toolmange.security.security;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Date;
import java.util.Objects;
import java.util.stream.Collectors;
/*
* @功能描述:security实体
* @作者:lr
* @时间:2019/8/12
* @备注:
*/
@Data
@AllArgsConstructor
public class JwtUser implements UserDetails {
@JsonIgnore
private final int id;
private final String username;
@JsonIgnore
private final String password;
private final String name;
@JsonIgnore
private final boolean enabled;
private final String icon;
@JsonIgnore
private Date createTime;
@JsonIgnore
private String remoteIp;
@JsonIgnore
private Collection<GrantedAuthority> authorities;
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public String getPassword() {
return password;
}
public Collection getRoles() {
return authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JwtUser jwtUser = (JwtUser) o;
return enabled == jwtUser.enabled &&
Objects.equals(username, jwtUser.username);
}
@Override
public int hashCode() {
return Objects.hash(username, enabled);
}
}
9.token 过滤类
package com.tools.toolmange.security.security;
import com.alibaba.fastjson.JSONObject;
import com.tools.toolmange.redis.RedisUtil;
import com.tools.toolmange.security.utils.JwtTokenUtil;
import com.tools.toolmange.constant.ConfigConstant;
import com.tools.toolmange.utils.ParamUtil;
import lombok.extern.log4j.Log4j2;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Log4j2
@Component
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
private final UserDetailsService userDetailsService;
private final JwtTokenUtil jwtTokenUtil;
private final String tokenHeader;
@Autowired
private RedisUtil redisUtil;
@Value("${jwt.expiration}")
private Long expiration;
@Value("${redis.token_name}")
private String tokenName;
public JwtAuthorizationTokenFilter(@Qualifier("jwtUserDetailsService") UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil, @Value("${jwt.header}") String tokenHeader) {
this.userDetailsService = userDetailsService;
this.jwtTokenUtil = jwtTokenUtil;
this.tokenHeader = tokenHeader;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
final String requestHeader = request.getHeader(this.tokenHeader);
String username = null;
String authToken = null;
JSONObject jon = new JSONObject();
jon.put("timestamp", System.currentTimeMillis());
jon.put("success", false);
if (requestHeader != null && requestHeader.startsWith("Bearer-")) {
authToken = requestHeader.substring(7);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
try {
//从令牌中 获取用户名
username = jwtTokenUtil.getUsernameFromToken(authToken);
} catch (Exception e) {
log.error(e);
jon.put("status", ConfigConstant.TOKEN_ERROR_CODE);
jon.put("message", ConfigConstant.TOKEN_ERROR_MSG);
response.getWriter().write(jon.toString());
return;
}
}
//SecurityContextHolder.getContext().getAuthentication() == null
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
/**
* 没有必要强制从数据库中加载使用细节。您还可以将信息存储在令牌中并从中读取。由你决定
*/
JwtUser userDetails = (JwtUser) this.userDetailsService.loadUserByUsername(username);
if (userDetails == null) {
jon.put("status", ConfigConstant.TOKEN_ERROR_CODE);
jon.put("message", ConfigConstant.USER_ERROR_MSG4);
response.getWriter().write(jon.toString());
return;
}
/**
* 对于简单的验证,只检查令牌完整性就足够了。您不必强制调用数据库。还是由你决定
*/
try {
//b = jwtTokenUtil.validateToken(authToken, userDetails);
//获取redis中的token
String rToken = ObjectUtils.toString(redisUtil.get(username + tokenName), "");
if (StringUtils.isBlank(rToken)) {
jon.put("status", ConfigConstant.TOKEN_ERROR_CODE);
jon.put("message", ConfigConstant.TOKEN_ERROR_MSG);
response.getWriter().write(jon.toString());
return;
}
if (!authToken.equals(rToken)) {
log.info(rToken);
jon.put("status", ConfigConstant.TOKEN_ERROR_CODE);
jon.put("message", ConfigConstant.TOKEN_OTHER_MSG);
response.getWriter().write(jon.toString());
return;
} else {
redisUtil.set(username + tokenName, authToken, expiration);
}
} catch (Exception e) {
jon.put("status", ConfigConstant.TOKEN_ERROR_CODE);
jon.put("message", e.getMessage());
response.getWriter().write(jon.toString());
return;
}
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
jon.clear();
chain.doFilter(request, response);
}
}
10.获取用户信息及角色的类
package com.tools.toolmange.security.service;
import com.tools.toolmange.bin.UserBO;
import com.tools.toolmange.dao.omis.UserMapper;
import com.tools.toolmange.security.security.JwtUser;
import org.apache.commons.lang.ObjectUtils;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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 javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
/*
* @功能描述:用户验证信息
* @作者:lr
* @时间:2019/8/7
* @备注:
*/
@Service
public class JwtUserDetailsService implements UserDetailsService {
@Resource
private UserMapper userMapper;
private final static String ADMIN_ID = "ADMIN";
private final static String ADMIN_PERMISSION = "ADMIN";
@Override
public UserDetails loadUserByUsername(String userCode) throws UsernameNotFoundException {
UserBO userBO = userMapper.queryUserInfo(userCode);
if (userBO == null) {
return null;
} else {
return createJwtUser(userBO);
}
}
private UserDetails createJwtUser(UserBO userBO){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try{
Date date = sdf.parse(ObjectUtils.toString(userBO.getCreateTime()));
return new JwtUser(
userBO.getId(),
userBO.getUserCode(),
userBO.getPassWord(),
userBO.getNickName(),
true,
userBO.getUserIcon(),
date,
"",
mapToGrantedAuthorities(userBO)
);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
private Collection<GrantedAuthority> mapToGrantedAuthorities(UserBO userBO) {
List<String> permissions = new ArrayList<>();
String userCode = userBO.getUserCode();
if(ADMIN_ID.equalsIgnoreCase(userCode)){
permissions.add(ADMIN_PERMISSION);
}else {
Map<String, Object> params = new HashMap<>();
params.put("id",userBO.getId());
permissions = userMapper.findPermissionByPosition(params);
}
return permissions.stream()
.map(permission -> new SimpleGrantedAuthority(permission))
.collect(Collectors.toList());
}
}
11.Redis 配置及工具类
package com.tools.toolmange.redis;
import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
//使用fastjson序列化
FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
// value值的序列化采用fastJsonRedisSerializer
template.setValueSerializer(fastJsonRedisSerializer);
template.setHashValueSerializer(fastJsonRedisSerializer);
// key的序列化采用StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean(StringRedisTemplate.class)
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
package com.tools.toolmange.redis;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public final class RedisUtil {
@Resource
private RedisTemplate<String, Object> redisTemplate;
public Set<String> keys(String keys){
try {
return redisTemplate.keys(keys);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入, 不存在放入,存在返回
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean setnx(String key, Object value) {
try {
redisTemplate.opsForValue().setIfAbsent(key,value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间,不存在放入,存在返回
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean setnx(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
/**
* 根据key获取Set中的所有值
* @param key 键
* @return
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
* @param key 键
* @return
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
* @param key 键
* @return
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
12.测试的controller 及 service
package com.tools.toolmange.security.controller;
import com.tools.toolmange.security.service.AuthenticationService;
import com.tools.toolmange.constant.ConfigConstant;
import com.tools.toolmange.utils.ParamUtil;
import com.tools.toolmange.utils.ResultUtil;
import com.tools.toolmange.bin.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("auth")
public class AuthenticationController {
@Resource
private AuthenticationService authenticationService;
@RequestMapping(value = "${jwt.auth.path}")
public Result login(@RequestBody Map<String, Object> params,HttpServletRequest request) {
ResultUtil rt = new ResultUtil();
//校验参数
Map<String, String> checkParams = new HashMap<>();
checkParams.put("userCode", "用户名");
checkParams.put("passWord", "密码");
String result = ParamUtil.checkMapParam(params, checkParams);
if (StringUtils.isNotEmpty(result)) {
return rt.setErrorMsg(ConfigConstant.SYSTEM_ERROR_CODE, result);
}
params.put("remoteIp",ParamUtil.getIpAddress(request));//获取用户请求ip校验ip和token
return authenticationService.authentication(params);
}
}
package com.tools.toolmange.security.service;
import com.tools.toolmange.redis.RedisUtil;
import com.tools.toolmange.security.security.AuthenticationInfo;
import com.tools.toolmange.security.security.JwtUser;
import com.tools.toolmange.security.utils.JwtTokenUtil;
import com.tools.toolmange.utils.Base64Util;
import com.tools.toolmange.constant.ConfigConstant;
import com.tools.toolmange.utils.ResultUtil;
import com.tools.toolmange.bin.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Map;
/*
* @功能描述:
* @作者:lr
* @时间:2019/9/11
* @备注:
*/
@Slf4j
@Service
public class AuthenticationService {
@Resource
private JwtTokenUtil jwtTokenUtil;
@Autowired
private RedisUtil redisUtil;
@Autowired
@Qualifier("jwtUserDetailsService")//唯一的实现限定
private UserDetailsService userDetailsService;
@Value("${jwt.expiration}")
private Long expiration;
@Value("${redis.token_name}")
private String tokenName;
/*
* @功能描述:登录逻辑
* @作者:lr
* @时间:2019/9/11
* @备注:
*/
public Result authentication(Map<String, Object> params) {
ResultUtil rt = new ResultUtil();
try {
//查询用户信息
final JwtUser authUser =
(JwtUser) userDetailsService.loadUserByUsername(ObjectUtils.toString(params.get("userCode")));
if (authUser == null)
return rt.setErrorMsg(ConfigConstant.USER_ERROR_CODE, ConfigConstant.USER_ERROR_MSG);
if (!authUser.getPassword().equals(ObjectUtils.toString(Base64Util.Encode(ObjectUtils.toString(params.get("passWord"))))))
return rt.setErrorMsg(ConfigConstant.USER_ERROR_CODE, ConfigConstant.USER_ERROR_MSG);
//放入ip
String remoteIp = ObjectUtils.toString(params.get("remoteIp"));
authUser.setRemoteIp(remoteIp);
//获取token信息
final String token = jwtTokenUtil.generateToken(authUser);
//存储token 有效期2小时
redisUtil.set(authUser.getUsername() + tokenName, token, expiration);
return rt.setData(new AuthenticationInfo(token, authUser));
} catch (Exception e) {
e.printStackTrace();
return rt.setErrorCodeMsg(ConfigConstant.ERROR_MSG);
}
}
}
13.数据库 :用户表,角色表,菜单表

本文详细介绍了如何使用SpringBoot、MyBatis、MySQL、Security和JWT搭建一个后台管理系统,涵盖项目创建、依赖配置、权限控制、JWT生成与验证、拦截器配置、Redis缓存管理及用户验证流程。
&spm=1001.2101.3001.5002&articleId=108515487&d=1&t=3&u=719d98cca8c84c5a89fd439bf8af7565)
5万+

被折叠的 条评论
为什么被折叠?



