SpringBoot(8) ------>集成SpringSecurity与Jwt
一、简介
1、基于Token的鉴权机制
基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或会话信息。这也就意味着基于tokent认证机制的应用不需要去考虑用户在哪一台服务器登陆了,这就为应用的扩展提供了便利
流程是这样的
- 用户使用用户名密码请求服务器
- 服务器进行验证用户信息(用户名、权限、部门、角色)
- 服务器通过验证发送给用户一个token
- 客户端存储token,并在每次请求时附加这个token值
- 服务器验证token,并返回数据
这个token必须要在每次请求时发送给服务器,它应该保存在请求头中,另外,服务器要支持CORS(跨来源资源共享)策略,一般在服务端设置"Access-Control-Allow-Origin:* " 就可以了
2、SpringSecurity
Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求.
Spring Security的控制力度很细,但是是依赖于spring。
3、Jwt的定义及组成
json web token是为了在网络应用环境中传递声明而执行的一种基于json的开放标准。特别适合分布式站点的单点登录场景。jwt的声明一般被用来在身份提供者和服务提供者之间传递被认证的用户身份信息,以便于从服务器获取资源,也可以增加一些额外的其他业务逻辑所用到的生命信息。
JWT(JSON Web Token)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。
一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。
1)头部(Header)
JWT需要一个头部,头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象:
{
"typ": "JWT",
"alg": "HS256"
}
在这里,说明了这是一个JWT,并且所用的签名算法是HS256算法。
对它也要进行Base64编码,之后的字符串就成了JWT的Header(头部):eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
2)载荷(Payload)
我们先将用户认证的操作描述成一个JSON对象。其中添加了一些其他的信息,帮助今后收到这个JWT的服务器理解这个JWT。
{
"sub": "1",
"iss": "http://localhost:8000/auth/login",
"iat": 1451888119,
"exp": 1454516119,
"nbf": 1451888119,
"jti": "37c107e4609ddbcc9c096ea5ee76c667"
}
这里面的前6个字段都是由JWT的标准所定义的。
- sub: 该JWT所面向的用户
- iss: 该JWT的签发者
- iat(issued at): 在什么时候签发的token
- exp(expires): token什么时候过期
- nbf(not before):token在此时间之前不能被接收处理
- jti:JWT ID为web token提供唯一标识
这些定义都可以在JWT 标准 中找到。
将上面的JSON对象进行base64编码可以得到下面的字符串:
eyJzdWIiOiIxIiwiaXNzIjoiaHR0cDpcL1wvbG9jYWx
ob3N0OjgwMDFcL2F1dGhcL2xvZ2luIiwiaWF0IjoxNDUxODg4MTE5LCJleHAiOjE0NTQ1MTYxMTksIm5iZiI6MTQ1MTg4OD
ExOSwianRpIjoiMzdjMTA3ZTQ2MDlkZGJjYzljMDk2ZWE1ZWU3NmM2NjcifQ
这个字符串我们将它称作JWT的Payload(载荷)。
如果你使用Node.js,可以用Node.js的包base64url来得到这个字符串:
var base64url = require('base64url')var header = {
"from_user": "B",
"target_user": "A"
}
console.log(base64url(JSON.stringify(header)))
注:Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。
3)签名(Signature)
将上面的两个编码后的字符串都用句号.连接在一起(头部在前),就形成了:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwiaXNzIjoiaHR0cDpcL1wvbG9j YWxob3N0OjgwMDFcL2F1dGhcL2xvZ2luIiwiaWF0IjoxNDUxODg4MTE5LCJleHAiOjE0N TQ1MTYxMTksIm5iZiI6MTQ1MTg4ODExOSwianRpIjoiMzdjMTA3ZTQ2MDlkZGJjYzljMD k2ZWE1ZWU3NmM2NjcifQ
最后,我们将上面拼接完的字符串用HS256算法进行加密。在加密的时候,我们还需要提供一个密钥(secret):
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
这样就可以得到我们加密后的内容:
wyoQ95RjAyQ2FF3aj8EvCSaUmeP0KUqcCJDENNfnaT4
这一部分又叫做签名。
最后将这一部分签名也拼接在被签名的字符串后面,我们就得到了完整的JWT:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwiaXNzIjoiaHR0cDpcL1wvbG9jYWxob3N0OjgwMDFcL2F
1dGhcL2xvZ2luIiwiaWF0IjoxNDUxODg4MTE5LCJleHAiOjE0NTQ1MTYxMTksIm5iZiI6MTQ1MTg4ODExOSwianRp
IjoiMzdjMTA3ZTQ2MDlkZGJjYzljMDk2ZWE1ZWU3NmM2NjcifQ.wyoQ95RjAyQ2FF3aj8EvCSaUmeP0KUqcCJDENNf
二、Springboot中集成SpringSecurity与JWT
1、向pom文件添加依赖

<!--security依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!--JWT(Json Web Token)登录支持--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-core</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>1.1.1</version> </dependency>
2、application.yml添加配置

#jwt配置 jwt: #定义我们的盐 密码 secret: mySecret #过期时间(单位s) expiration: 1800 #token 的类型 说明他以 bearer 开头 tokenHead: bearer #token 对应的 key tokenHeader: Authorization # {Authorization: "bearer sdfdsfsdfsdfdsfsdfadfdsf"}
3、config配置
1)FilterConfig(跨域配置)

/** * @author liangd * date 2020-12-10 16:00 * code 跨域配置 * .@Order 注解用来声明组件的顺序,值越小,优先级越高,越先被执行/初始化。如果没有该注解,则优先级最低。 */ @Configuration @Order(1) public class FilterConfig { private CorsConfiguration buildConfig(){ CorsConfiguration corsConfiguration = new CorsConfiguration(); // 允许任何的head头部 corsConfiguration.addAllowedHeader("*"); // 允许任何域名使用 corsConfiguration.addAllowedOrigin("*"); // 允许任何的请求方法 corsConfiguration.addAllowedMethod("*"); corsConfiguration.setAllowCredentials(true); return corsConfiguration; } /** * 添加CorsFilter拦截器,对任意的请求使用 * @return CorsFilter */ @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", buildConfig()); return new CorsFilter(source); } /* 配置security-jwt步骤总结: 1、引入pom依赖 2、配置yml 3、新建vo登录参数 LoginParams 4、创建jwt工具类 JwtTokenUtil 5、配置Jwt登录授权过滤器 JwtAuthenticationTokenFilter 6、实现登录生成token逻辑 SysUserController 7、跨域配置 FilterConfig 8、配置当未登录或者token失效访问接口时,自定义的返回结果 RestAuthenticationEntryPoint 9、配置访问接口没有权限时,自定义的返回结果 RestfulAccessDeniedHandler */ }
2)SecurityConfig(全局配置)

/** * @author liangd * date 2020-12-10 14:43 * <p> * .@EnableGlobalMethodSecurity 是否允许方法上设置权限 * prePostEnabled 方法执行前/后 默认为false */ @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomUserDetailsServiceImpl userDetailsService; @Autowired private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; @Autowired private RestAuthenticationEntryPoint restAuthenticationEntryPoint; @Autowired private RestfulAccessDeniedHandler restfulAccessDeniedHandler; /** * 跨域配置 */ @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and() .csrf().disable() // 基于token,所以不需要 securityContext .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() //放行的资源路径 .antMatchers("/css/**", "/js/**", "/fonts/**", "/user/login", "/user/register").permitAll() .antMatchers(HttpMethod.OPTIONS).permitAll() //任何请求都需要认证 .anyRequest().authenticated() .and() .userDetailsService(userDetailsService) ; //自定义 登录界面 http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); //添加自定义未授权和未登录结果返回 http.exceptionHandling() .accessDeniedHandler(restfulAccessDeniedHandler) .authenticationEntryPoint(restAuthenticationEntryPoint); } /*@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() //设置哪些路径可以访问 .antMatchers("/css/**", "/js/**", "/fonts/**").permitAll() //需要相应的角色才能访问 .antMatchers("/users/**").hasRole("ADMIN") .anyRequest().authenticated() // 任何请求都需要认证 .and() .formLogin() //基于Form表单登录验证 .and() .userDetailsService(userDetailsService); }*/ /** * 创建passwordEncoder对象 */ @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
4、filter
JwtAuthenticationTokenFilter(JWT登录授权过滤器)

package com.donleo.security.jwt.filter; import com.donleo.security.jwt.utils.JwtTokenUtil; 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.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.WebAuthenticationDetails; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; 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; /** * @author liangd * date 2020-12-10 15:39 * code JWT登录授权过滤器 */ @Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class); @Autowired private UserDetailsService userDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; /** * Authorization */ @Value("${jwt.tokenHeader}") private String tokenHeader; /** * bearer */ @Value("${jwt.tokenHead}") private String tokenHead; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { //从 header 中获取 Authorization String authHeader = request.getHeader(this.tokenHeader); // 判断 authHeader 不为空 并且以 bearer 开头 if (authHeader != null) { boolean b1 = StringUtils.startsWithIgnoreCase(authHeader,this.tokenHead); if (b1) { //截取 bearer 后面的字符串 并且 两端去空格(获取token)// The part after "Bearer " String authToken = authHeader.substring(this.tokenHead.length()).trim(); String username = jwtTokenUtil.getUserNameFromToken(authToken); LOGGER.info("checking username:{}", username); // 用户名不为空 并且SecurityContextHolder.getContext() 存储 权限的容器中没有相关权限则继续 boolean b = SecurityContextHolder.getContext().getAuthentication() == null; if (username != null && b) { //从数据库读取用户信息 UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); //校验token if (jwtTokenUtil.validateToken(authToken, userDetails)) { //UsernamePasswordAuthenticationToken(var1,var2,var3) //第二个参数相当于密码校验 //第三个参数为用户拥有的权限校验 UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); WebAuthenticationDetails details = new WebAuthenticationDetailsSource().buildDetails(request); //设置用户ip authentication.setDetails(details); LOGGER.info("authenticated user:{}", username); //存入本线程的安全容器 在访问接口拿到返回值后 要去主动清除 权限,避免干扰其他的线程 SecurityContextHolder.getContext().setAuthentication(authentication); } } } } chain.doFilter(request, response); } /* Jwt登录拦截过滤器 1、拦截用户请求、获取请求头 2、判断请求头是否为空并且是否是以bearer开头 3、截取bearer后面的字符串并去掉两端空格获取token 4、从token中获取用户名 5、判断用户名不为空,并且SecurityContextHolder.getContext() 存储权限的容器中没有相关权限则继续 6、根据用户名查询数据库 7、校验token,判断用户是否登录、是否有权限以及token是否过期 8、校验该用户拥有的权限 9、存入用户ip (0:0:0:0:0:0:0:1) 10、存入本线程的安全容器 在访问接口拿到返回值后 要去主动清除 权限,避免干扰其他的线程 */ }
5、utils工具类
1)JwtTokenUtil(生成token以及解析token工具类)

/** * @author liangd * date 2020-12-10 15:10 * code JwtToken生成的工具类 * <p> * JWT token的格式:header.payload.signature * header的格式(算法、token的类型): * {"alg": "HS512","typ": "JWT"} * payload的格式(用户名、创建时间、生成时间): * {"sub":"wang","created":1489079981393,"exp":1489684781} * signature的生成算法: * HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret) */ @Component public class JwtTokenUtil { private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class); private static final String CLAIM_KEY_USERNAME = "sub"; private static final String CLAIM_KEY_CREATED = "created"; /** * 盐 */ @Value("${jwt.secret}") private String secret; /** * 过期时间 */ @Value("${jwt.expiration}") private Long expiration; /** * 根据 负载(用户名 部门 权限 等) 生成JWT的token */ private String generateToken(Map<String, Object> claims) { return Jwts.builder() .setClaims(claims) .setExpiration(generateExpirationDate()) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } /** * 从token中获取JWT中的负载 */ private Claims getClaimsFromToken(String token) { Claims claims = null; try { claims = Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); } catch (Exception e) { LOGGER.info("JWT格式验证失败:{}", token); } return claims; } /** * 生成token的过期时间 *1000,秒换成毫秒 */ private Date generateExpirationDate() { return new Date(System.currentTimeMillis() + expiration * 1000); } /** * 从token中获取登录用户名 */ public String getUserNameFromToken(String token) { String username; try { Claims claims = getClaimsFromToken(token); username = claims.getSubject(); } catch (Exception e) { username = null; } return username; } /** * 验证token是否还有效 * * @param token 客户端传入的token * @param userDetails 从数据库中查询出来的用户信息 */ public boolean validateToken(String token, UserDetails userDetails) { String username = getUserNameFromToken(token); return username.equals(userDetails.getUsername()) && !isTokenExpired(token); } /** * 判断token是否已经失效 */ private boolean isTokenExpired(String token) { Date expiredDate = getExpiredDateFromToken(token); return expiredDate.before(new Date()); } /** * 从token中获取过期时间 */ private Date getExpiredDateFromToken(String token) { Claims claims = getClaimsFromToken(token); return claims.getExpiration(); } /** * 根据用户信息生成token * jwt中的token由三部分组成 ----头部、载荷和签名 * 1、头部(bearer) ------放在最前 * 2、载荷(用户认证信息) ------放在中间(以"."拼接) * 3、签名(密钥secret) ------放在最后 * * 这里根据用户名和时间生成token * * eyJhbGciOiJIUzUxMiJ9 * .eyJzdWIiOiJhZG1pbiIsImNyZWF0ZWQiOjE2MDc1OTIyODYxOTAsImV4cCI6MTYwNzU5NDE1Nn0 * .apZ0vpePj-yHpM9K444GbeB-R3EMmpx3XPBchb--IB6EDB1g_036fHfohunK1g-M2Grli8Ncg0FT6Oksk_Jx_g */ public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername()); claims.put(CLAIM_KEY_CREATED, new Date()); return generateToken(claims); } /** * 判断token是否可以被刷新 */ public boolean canRefresh(String token) { return !isTokenExpired(token); } /** * 刷新token */ public String refreshToken(String token) { Claims claims = getClaimsFromToken(token); claims.put(CLAIM_KEY_CREATED, new Date()); return generateToken(claims); } }
2)RestAuthenticationEntryPoint(未登录返回结果)

package com.donleo.security.jwt.utils; import cn.hutool.json.JSONUtil; import com.donleo.security.jwt.common.CommonResult; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author liangd * date 2020-12-10 15:50 * code 当未登录或者token失效访问接口时,自定义的返回结果 */ @Component public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json"); response.getWriter().println(JSONUtil.parse(CommonResult.unauthorized(authException.getMessage()))); response.getWriter().flush(); } }
3)RestfulAccessDeniedHandler(没有权限返回结果)

package com.donleo.security.jwt.utils; import cn.hutool.json.JSONUtil; import com.donleo.security.jwt.common.CommonResult; 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; /** * @author liangd * date 2020-12-10 15:56 * code 当访问接口没有权限时,自定义的返回结果 */ @Component public class RestfulAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json"); response.getWriter().println(JSONUtil.parse(CommonResult.forbidden(e.getMessage()))); response.getWriter().flush(); } }
6、model层
1)SysUser(实现UserDetails)

package com.donleo.security.jwt.model; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.Date; import java.util.Set; /** * @author liangd * date 2020-12-08 18:23 * code 用户表 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor @TableName(value = "sys_user") public class SysUser implements UserDetails { @TableId(value = "id",type = IdType.AUTO) private Integer id; private String username; private String password; private String icon; private String email; private String nickName; private String note; private Date createTime; private Date loginTime; private Integer status; /** * .@JsonIgnoreProperties防止存redis反序列化时报错 */ @JsonIgnoreProperties(ignoreUnknown = true) private Set<? extends GrantedAuthority> authorities; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return this.authorities; } @Override public String getPassword() { return this.password; } @Override public String getUsername() { return this.username; } @Override public boolean isAccountNonExpired() { return true; } public void setAccountNonExpired(boolean accountNonExpired) { } @Override public boolean isAccountNonLocked() { return true; } public void setAccountNonLocked(boolean accountNonLocked) { } @Override public boolean isCredentialsNonExpired() { return true; } public void setCredentialsNonExpired(boolean credentialsNonExpired) { } @Override public boolean isEnabled() { if(this.status==null){ return false; } return this.status==1; } public void setEnabled(boolean enabled) { } }
2)SysPermission(实现GrantedAuthority)

package com.donleo.security.jwt.model; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.security.core.GrantedAuthority; import java.util.Date; /** * @author liangd * date 2020-12-08 18:28 * code 权限表 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor @TableName(value = "sys_permission") public class SysPermission implements GrantedAuthority { @TableId(value = "id",type = IdType.AUTO) private Integer id; private Integer pid; private String name; /** * 权限值 */ private String value; private String icon; private Integer type; private String uri; private Integer status; private Date createTime; private String sort; @Override public String getAuthority() { return this.value; } }
7、service层
ISysUserService(用户接口定义)

package com.donleo.security.jwt.service; import com.donleo.security.jwt.model.SysPermission; import com.donleo.security.jwt.model.SysUser; import com.donleo.security.jwt.vo.LoginParams; import java.util.List; /** * @author liangd * date 2020-12-10 14:46 * code */ public interface ISysUserService { /** * 根据用户名查询用户 * @param name 用户名 * @return SysUser */ SysUser getUserByName(String name); /** * 根据用户Id查询用户权限 * @param id 用户Id * @return List */ List<SysPermission> getPermissionsByUserId(Integer id); /** * 用户登录 * @param loginParams LoginParams * @return String */ String login(LoginParams loginParams); /** * 用户注册 * @param sysUser SysUser * @return Boolean */ Boolean register(SysUser sysUser); }
8、serviceImpl层
1)SysUserServiceImpl(用户接口实现)

package com.donleo.security.jwt.service.impl; import com.donleo.security.jwt.mapper.ISysUserMapper; import com.donleo.security.jwt.model.SysPermission; import com.donleo.security.jwt.model.SysUser; import com.donleo.security.jwt.service.ISysUserService; import com.donleo.security.jwt.utils.JwtTokenUtil; import com.donleo.security.jwt.vo.LoginParams; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import java.util.Date; import java.util.List; /** * @author liangd * date 2020-12-10 14:47 * code 用户逻辑实现层 */ @Service public class SysUserServiceImpl implements ISysUserService { @Autowired private ISysUserMapper sysUserMapper; @Autowired private PasswordEncoder passwordEncoder; @Autowired private JwtTokenUtil jwtTokenUtil; @Override public SysUser getUserByName(String name) { List<SysUser> users = sysUserMapper.getUserByName(name); Assert.isTrue(users.size() == 1, "您输入的账户不存在,或者有多个相同的账户"); return users.get(0); } @Override public List<SysPermission> getPermissionsByUserId(Integer id) { return sysUserMapper.getPermissionsByUserId(id); } @Override public String login(LoginParams loginParams) { String username = loginParams.getUsername(); Assert.notNull(username, "账号必须不能为空"); String password = loginParams.getPassword(); Assert.notNull(password, "密码必须不能为空"); SysUser userByName = getUserByName(username); //判断用户输入的密码与数据库中查出来的密码是否相等 boolean matches = passwordEncoder.matches(password, userByName.getPassword()); //如果密码相等,表明用户信息输入正确,生成一个token令牌,否则返回null if (matches) { return jwtTokenUtil.generateToken(userByName); } return null; /* 生成token逻辑: 根据用户的信息(例如用户名)、token的过期时间和密匙等通过base64加密生成token, 最后拼接上头部信息,返回jwt token字符串 */ } @Override public Boolean register(SysUser sysUser) { String username = sysUser.getUsername(); Assert.notNull(username, "用户名不能为空"); String password = sysUser.getPassword(); Assert.notNull(password, "密码不能为空"); List<SysUser> user = sysUserMapper.getUserByName(username); if (user.size() >= 1) { return false; } sysUser.setPassword(passwordEncoder.encode(password)); sysUser.setCreateTime(new Date()); sysUserMapper.insert(sysUser); return true; } }
2)CustomUserDetailsService(自定义UserDetailsService)

package com.donleo.security.jwt.service.impl; import com.donleo.security.jwt.model.SysPermission; import com.donleo.security.jwt.model.SysUser; import com.donleo.security.jwt.service.ISysUserService; import org.springframework.beans.factory.annotation.Autowired; 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 java.util.HashSet; import java.util.List; /** * @author liangd * date 2020-12-08 19:10 * code 自定义UserDetailsService */ @Service("userDetailsService") public class CustomUserDetailsServiceImpl implements UserDetailsService { @Autowired ISysUserService sysUserService; /** * 从数据库读取用户名认证 * * @param username 用户名 * @return UserDetails * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUser user = sysUserService.getUserByName(username); List<SysPermission> permissionList = sysUserService.getPermissionsByUserId(user.getId()); //获取用户拥有的权限 HashSet<SysPermission> permissions = new HashSet<>(permissionList); user.setAuthorities(permissions); return user; } }
9、持久层
ISysUserMapper(dao层接口定义)

/** * @author liangd * date 2020-12-10 14:37 * code */ public interface ISysUserMapper extends BaseMapper<SysUser> { List<SysUser> getUserByName(String name); List<SysPermission> getPermissionsByUserId(Integer id); }
10、mapper
sql语句

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.donleo.security.jwt.mapper.ISysUserMapper"> <!--根据用户名查询用户--> <select id="getUserByName" parameterType="java.lang.String" resultType="com.donleo.security.jwt.model.SysUser"> select * from sys_user where username =#{name} </select> <!--根据用户Id查询用户权限--> <select id="getPermissionsByUserId" parameterType="java.lang.Integer" resultType="com.donleo.security.jwt.model.SysPermission"> select * from sys_permission p where p.id in( select rp.permission_id from sys_role_permission_relation rp where rp.role_id in (select ur.role_id from sys_user_role_relation ur WHERE ur.user_id =#{userId}) UNION SELECT up.permission_id from sys_user_permission_relation up WHERE up.type=1 and up.user_id=#{userId} ) and p.id not in( SELECT up.permission_id from sys_user_permission_relation up WHERE up.type=-1 and up.user_id=#{userId} ) </select> <!--查询所有--> <select id="findAll" resultType="com.donleo.security.jwt.model.SysUser"> select * from sys_user </select> <!--查询单个--> <select id="findById" resultType="SysUser"> select * from sys_user where id = #{id,jdbcType=INTEGER} </select> </mapper>
11、控制层(测试)
1)SysUserController(登陆注册)
登录成功生成token

package com.donleo.security.jwt.controller; import com.donleo.security.jwt.common.CommonResult; import com.donleo.security.jwt.model.SysUser; import com.donleo.security.jwt.service.ISysUserService; import com.donleo.security.jwt.vo.LoginParams; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; /** * @author liangd * date 2020-12-10 15:32 * code 用户controller */ @RestController @RequestMapping("/user") public class SysUserController { @Value("${jwt.tokenHead}") private String tokenHead; @Autowired private ISysUserService userService; /** * 用户登录 * @param loginParams * @return */ @PostMapping("/login") public CommonResult login(@RequestBody LoginParams loginParams){ HashMap<String, String> data = new HashMap<>(); String token = null; try { token = userService.login(loginParams); } catch (Exception e) { e.printStackTrace(); return CommonResult.validateFailed("用户名或密码错误"); } if (StringUtils.isEmpty(token)){ return CommonResult.validateFailed("用户名或密码错误"); } data.put("tokenHead",tokenHead); data.put("access_token",token); // localStorage.setItem("Authorization","Bearer sdsdfdfds") // $ajax{data:{},type:"",header:{"Authorization":"Bearer sdsdfdfds"}} return CommonResult.success(data); } /** * 用户注册 * @param sysUser * @return */ @PostMapping("/register") public CommonResult register(@RequestBody SysUser sysUser){ boolean b = userService.register(sysUser); if (b){ return CommonResult.success("注册成功"); } return CommonResult.failed("账号已存在"); } }
2) SysRoleController(方法上添加权限验证)
添加@PreAuthorize 在执行此方法之前验证权限

package com.donleo.security.jwt.controller; import com.donleo.security.jwt.common.CommonResult; import com.donleo.security.jwt.mapper.ISysRoleMapper; import com.donleo.security.jwt.model.SysRole; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; /** * @author liangd * date 2020-12-10 15:00 * code 系统角色控制层 */ @RestController @RequestMapping("/role") public class SysRoleController { @Autowired private ISysRoleMapper sysRoleMapper; @PreAuthorize("hasAuthority('wx:product:readtest')") @GetMapping("/findById") public CommonResult findById(Integer id){ SysRole sysRole = sysRoleMapper.selectById(id); return CommonResult.success(sysRole); } @PreAuthorize("hasAuthority('wx:product:read')") @GetMapping("/findAll") public CommonResult findAll(){ List<SysRole> sysRoleList = sysRoleMapper.selectList(null); return CommonResult.success(sysRoleList); } }
源码地址:https://gitee.com/donleo/springboot-security-jwt
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix