1.创建数据库表
2.创建项目导入依赖 mysql secruity lombok jwt mp
<?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.5.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>zfc-spring-security</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>zfc-spring-security</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-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</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>
<!--jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</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>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
3.添加配置 mysql jwt 过期时间 请求头 盐值
server.port=7777
spring.datasource.username=test
spring.datasource.password=test
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/zfc_test?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#请求头
jwt.header=Authorization
#盐值
jwt.base64-secret=meng
#过期时间
jwt.token-validity-in-seconds=14400000
4.创建实体类
package com.example.zfcspringsecurity.model;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @ClassName: JwtProperties
* @Description:
* @Author: zfc
* @Date: 2021/8/16 15:38
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "jwt") //与配置文件中的数据关联起来(这个注解会自动匹配jwt开头的配置)
public class JwtProperties {
/** Request Headers : Authorization */
private String header;
/** Base64对该令牌进行编码 */
private String base64Secret;
/** 令牌过期时间 此处单位/毫秒 */
private Long tokenValidityInSeconds;
}
5.写mapper
6.service
impl 要实现security UserDetailsService 此处验证登陆账号是否存在,存在就查询到账号对象,再放入security的user对象返回出去,让security做密码判断
package com.example.zfcspringsecurity.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.zfcspringsecurity.mapper.userMapper;
import com.example.zfcspringsecurity.model.JwtUser;
import com.example.zfcspringsecurity.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
/**
* @ClassName: userServiceImpl
* @Description:
* @Author: zfc
* @Date: 2021/8/9 15:22
*/
@Service
public class userServiceImpl implements UserDetailsService {
@Autowired
private userMapper userMapper;
@Autowired
private PasswordEncoder pw;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("username="+username);
QueryWrapper<User> wrapper=new QueryWrapper<>();
wrapper.eq("username",username);
User zfcUser=userMapper.selectOne(wrapper);
System.out.println("zfcUser="+zfcUser);
String password=pw.encode(zfcUser.getPassword());
System.out.println(password);
org.springframework.security.core.userdetails.User user=
new org.springframework.security.core.userdetails.User(zfcUser.getUsername(),password,
AuthorityUtils.commaSeparatedStringToAuthorityList(zfcUser.getRole()));
return user;
}
}
7.控制器
package com.example.zfcspringsecurity.controller;
import com.example.zfcspringsecurity.mapper.userMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @ClassName: userController
* @Description:
* @Author: zfc
* @Date: 2021/8/9 15:07
*/
@CrossOrigin
@RestController
public class userController {
@RequestMapping("/toMain")
public String toMain(){
return "登陆成功";
}
@RequestMapping("/toError")
public String toError(){
return "登陆失败";
}
@RequestMapping("/index")
public String index(){
return "所有人都可以访问的首页";
}
@RequestMapping("/zfc")
public String zfc(){
return "zfc权限";
}
@RequestMapping("/login")
public String login(){
return "请先登陆";
}
}
8.创建jwt工具类,网上找的
package com.example.zfcspringsecurity.utils;
import com.example.zfcspringsecurity.model.JwtProperties;
import com.example.zfcspringsecurity.model.JwtUser;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName: JwtTokenUtil
* @Description:
* @Author: zfc
* @Date: 2021/8/16 15:52
*/
@Component
public class JwtTokenUtil {
// 注入自己的jwt配置
@Resource
private JwtProperties jwtProperties;
static final String CLAIM_KEY_USERNAME = "sub";
static final String CLAIM_KEY_AUDIENCE = "audience";
static final String CLAIM_KEY_CREATED = "created";
private static final String AUDIENCE_UNKNOWN = "unknown";
private static final String AUDIENCE_WEB = "web";
private static final String AUDIENCE_MOBILE = "mobile";
private static final String AUDIENCE_TABLET = "tablet";
public String getUsernameFromToken(String token) {
String username;
try {
final Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
public Date getCreatedDateFromToken(String token) {
Date created;
try {
final Claims claims = getClaimsFromToken(token);
created = new Date((Long) claims.get(CLAIM_KEY_CREATED));
} catch (Exception e) {
created = null;
}
return created;
}
public Date getExpirationDateFromToken(String token) {
Date expiration;
try {
final Claims claims = getClaimsFromToken(token);
//得到token的有效期
expiration = claims.getExpiration();
} catch (Exception e) {
expiration = null;
}
return expiration;
}
public String getAudienceFromToken(String token) {
String audience;
try {
final Claims claims = getClaimsFromToken(token);
audience = (String) claims.get(CLAIM_KEY_AUDIENCE);
} catch (Exception e) {
audience = null;
}
return audience;
}
private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(jwtProperties.getBase64Secret())
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
claims = null;
}
return claims;
}
//设置过期时间
private Date generateExpirationDate() {
return new Date(System.currentTimeMillis() + jwtProperties.getTokenValidityInSeconds());
// return new Date(30 * 24 * 60);
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
return (lastPasswordReset != null && created.before(lastPasswordReset));
}
// Device用户检测当前用户的设备,用不到的话可以删掉(使用这个需要添加相应的依赖)
// private String generateAudience(Device device) {
// String audience = AUDIENCE_UNKNOWN;
// if (device.isNormal()) {
// audience = AUDIENCE_WEB;
// } else if (device.isTablet()) {
// audience = AUDIENCE_TABLET;
// } else if (device.isMobile()) {
// audience = AUDIENCE_MOBILE;
// }
// return audience;
// }
private Boolean ignoreTokenExpiration(String token) {
String audience = getAudienceFromToken(token);
return (AUDIENCE_TABLET.equals(audience) || AUDIENCE_MOBILE.equals(audience));
}
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, username);
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
/**
* 生成token(最关键)
* @param claims
* @return
*/
String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims) //设置声明信息(用户名等)
.setExpiration(generateExpirationDate()) //设置过期时间
.signWith(SignatureAlgorithm.HS512, jwtProperties.getBase64Secret()) //设置签名
.compact();
}
public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
final Date created = getCreatedDateFromToken(token);
return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
&& (!isTokenExpired(token) || ignoreTokenExpiration(token));
}
public String refreshToken(String token) {
String refreshedToken;
try {
final Claims claims = getClaimsFromToken(token);
claims.put(CLAIM_KEY_CREATED, new Date());
refreshedToken = generateToken(claims);
} catch (Exception e) {
refreshedToken = null;
}
return refreshedToken;
}
//TODO,验证当前的token是否有效
public Boolean validateToken(String token, UserDetails userDetails) {
// JwtUser user = (JwtUser) userDetails;
// System.out.println("aaaaaaaaaaaaaaaaaaaa======"+user.getUsername()+"//"+userDetails.getUsername());
final String username = getUsernameFromToken(token);
final Date created = getCreatedDateFromToken(token);
// return (username.equals(user.getUsername())&& !isTokenExpired(token));
return (username.equals(userDetails.getUsername())&& !isTokenExpired(token));
}
}
9.创建jwt 登陆拦截器,拦截登陆请求,将获取到的表单信息拿用户名去impl获取UserDetails 对象,再将前端密码和UserDetails 密码做判断
package com.example.zfcspringsecurity.filter;
import com.example.zfcspringsecurity.model.JwtProperties;
import com.example.zfcspringsecurity.service.impl.userServiceImpl;
import com.example.zfcspringsecurity.utils.JwtTokenUtil;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @ClassName: JwtAuthenticationTokenFilter
* @Description:
* @Author: zfc
* @Date: 2021/8/16 16:00
*/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Resource
private userServiceImpl userDetailsService;
@Resource
private JwtTokenUtil jwtTokenUtil;
@Resource
private JwtProperties jwtProperties;
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String requestUrl = httpServletRequest.getRequestURI();
String authToken = httpServletRequest.getHeader(jwtProperties.getHeader());
String stuId = jwtTokenUtil.getUsernameFromToken(authToken);
System.out.println("进入自定义过滤器");
System.out.println("自定义过滤器获得用户名为 "+stuId);
//当token中的username不为空时进行验证token是否是有效的token
if (stuId != null && SecurityContextHolder.getContext().getAuthentication() == null) {
//token中username不为空,并且Context中的认证为空,进行token验证
//TODO,从数据库得到带有密码的完整user信息
UserDetails userDetails = this.userDetailsService.loadUserByUsername(stuId);
if (jwtTokenUtil.validateToken(authToken, userDetails)) { //如username不为空,并且能够在数据库中查到
/**
* UsernamePasswordAuthenticationToken继承AbstractAuthenticationToken实现Authentication
* 所以当在页面中输入用户名和密码之后首先会进入到UsernamePasswordAuthenticationToken验证(Authentication),
* 然后生成的Authentication会被交由AuthenticationManager来进行管理
* 而AuthenticationManager管理一系列的AuthenticationProvider,
* 而每一个Provider都会通UserDetailsService和UserDetail来返回一个
* 以UsernamePasswordAuthenticationToken实现的带用户名和密码以及权限的Authentication
*/
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
//将authentication放入SecurityContextHolder中
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
10.创建springsecurity配置文件 使用自定义的jwt过滤器,验证密码。使用自定义的登陆成功跳转
package com.example.zfcspringsecurity.config;
import com.example.zfcspringsecurity.filter.JwtAuthenticationTokenFilter;
import com.example.zfcspringsecurity.security.MyAuthenticationSuccessHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
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 javax.annotation.Resource;
/**
* @ClassName: SecurityConfig
* @Description:
* @Author: zfc
* @Date: 2021/8/12 9:28
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SpringSecurityConf extends WebSecurityConfigurerAdapter {
@Resource
private MyAuthenticationSuccessHandler jwtAuthenticationSuccessHandler;
// 自定义的Jwt Token过滤器
@Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
return new JwtAuthenticationTokenFilter();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//自定义登陆页面
http.formLogin()
.loginPage("/login")
//自定义认证成功处理器
.successHandler(jwtAuthenticationSuccessHandler)
.failureForwardUrl("/toError").permitAll()
.and()
//设置无状态的连接,即不创建session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/login").permitAll()
//配置允许匿名访问的路径
.anyRequest().authenticated();
//关闭csrf
http.csrf().disable();
//跨域
http.cors();
//配置自己的jwt验证过滤器
http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
// disable page caching
http.headers().cacheControl();
}
@Bean
public PasswordEncoder pw(){
return new BCryptPasswordEncoder();
}
}
11.自定义登陆成功跳转 登陆成功时执行,返回一个tocken给前端
package com.example.zfcspringsecurity.security;
import com.example.zfcspringsecurity.utils.JwtTokenUtil;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
/**
* @ClassName: MyAuthenticationSuccessHandler
* @Description:自定义认证成功处理器
* @Author: zfc
* @Date: 2021/8/16 15:54
*/
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Resource
private JwtTokenUtil jwtTokenUtil;
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
//生成token
final String realToken = jwtTokenUtil.generateToken(authentication.getName());
//将生成的authentication放入容器中,生成安全的上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
httpServletResponse.setContentType("text/json;charset=utf-8");
//登陆成功把token写入 cookie返回给前端,也可以通过head返回
Cookie cookie=new Cookie("token",realToken);
httpServletResponse.addCookie(cookie);
}
}
12.前端 form表单方式,网上随便拉个模板,自己写个表单提交也一样
13.测试,此时登陆成功就可以在cookie拿到token,请求其他接口时只要head带上token就行
请求其他接口(这里使用postman)
14.表单提交成功时会自动刷新页面到提交接口,这里放一个网上看到前辈的解决方式。emmmmmmmmm,我第一眼看到时候就是卧槽,让我想起了之前看到的弹窗显示清楚缓存的前辈,对前辈们的敬佩如滔滔江水,哈哈哈哈哈
15.关于表单提交刷新页面的和获取数据的新解决办法
引入jqueryform,把表单提交换成下面这种方式
后端将自定义成功返回改成json格式
完成