springsecurity6前后端分离
一、数据库
1、user表

2、user_login表

3、role表

4、user_role表

5、authority表

6、role_authority表

7、resource表

8、resource_authority表

二、springsecurity
1、pom.xml
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.5.6</version> <relativePath/> <!-- lookup parent from repository --> </parent> ...... <dependencies> <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-test</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> </dependency> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>2.0.59</version> </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>4.5.0</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.42</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.0.32</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> </dependencies>
2、application.yml
server: port: 8080 servlet: encoding: charset: UTF-8 enabled: true force: true tomcat: uri-encoding: UTF-8 spring: application: name: VUE_SECURITY datasource: url: jdbc:mysql://localhost:3306/vue-security?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true username: xxx password: xxx driver-class-name: com.mysql.cj.jdbc.Driver hikari: connection-timeout: 30000 maximum-pool-size: 10 idle-timeout: 600000 max-lifetime: 1800000 minimum-idle: 5 connection-test-query: SELECT 1 mybatis: configuration: #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #控制台打印sql信息 map-underscore-to-camel-case: true mapper-locations: classpath:mapper/**/*Mapper.xml type-aliases-package: com.example.model
3、mapper文件
a、UserMapper.xml
<select id="getCustomUserDetailsByUsername" parameterType="String" resultMap="customUserDetailsResult"> select t.login_name, t.phone, t.password, t2.code as role_code, t4.code as authority_codes, t5.name from user_login t inner join user_role t1 on t.user_id = t1.user_id and t1.status = 1 and t1.seq = 1 inner join role t2 on t1.role_id = t2.id and t2.status = 1 inner join role_authority t3 on t2.id = t3.role_id and t3.status = 1 inner join authority t4 on t3.authority_id = t4.id and t4.status = 1 inner join user t5 on t.user_id = t5.id and t5.status =1 <where> t.status = 1 <if test="username != null"> and (t.login_name = #{username} or t.phone = #{username}) </if> </where> </select> <resultMap id="customUserDetailsResult" type="customUserDetails"> <result column="password" property="password"/> <result column="name" property="name"/> <result column="login_name" property="email"/> <result column="phone" property="phone"/> <result column="role_code" property="role"/> <collection property="authorityCodes" ofType="string" javaType="list"> <result column="authority_codes"/> </collection> </resultMap>
b、AuthorityMapper.xml
<select id="isAnonymousAllowed"> select t.id from authority t inner join resource_authority t1 on t.id = t1.authority_id and t1.status = 1 inner join resource t2 on t1.resource_id = t2.id and t2.status = 1 <if test="urls != null"> and <foreach collection="list" item="url" open="(" separator="or" close=")"> (t2.url = #{url} and t2.method = #{method}) </foreach> </if> where t.status = 1 and t.code = 'user_anonymous' </select> <select id="checkAuthority"> select t.id from authority t inner join resource_authority t1 on t.id = t1.authority_id and t1.status = 1 inner join resource t2 on t1.resource_id = t2.id and t2.status = 1 <if test="urls != null"> and <foreach collection="urls" item="url" open="(" separator="or" close=")"> (t2.url = #{url} and t2.method = #{method}) </foreach> </if> where t.status = 1 <if test="codes != null"> and t.code in <foreach collection="codes" item="code" open="(" separator="," close=")"> #{code} </foreach> </if> </select>
c、ResourceMapper.xml
<select id="exists" resultType="Long"> select t.id from resource t where t.status = 1 <if test="url != null"> and t.url = #{url} </if> <if test="method != null"> and t.method = #{method} </if> <if test="url == null || method == null"> and 1 = 2 </if> </select>
4、model包
a、CustomUserDetails.java
import java.util.Collection; import java.util.List; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; /** * @author Administrator */ @NoArgsConstructor @AllArgsConstructor public class CustomUserDetails implements UserDetails { private String username; private String password; private String name; private String email; private String phone; private String role; private Collection<? extends GrantedAuthority> authorities; private List<String> authorityCodes; public CustomUserDetails(String role, Collection<? extends GrantedAuthority> authorities) { this.role = role; this.authorities = authorities; } public CustomUserDetails(String username, String password, String name, String email, String phone, String role, Collection<? extends GrantedAuthority> authorities) { this.username = username; this.password = password; this.name = name; this.email = email; this.phone = phone; this.role = role; this.authorities = authorities; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return this.authorities; } @Override public String getPassword() { return this.password; } @Override public String getUsername() { return this.username; } public String getName() { return name; } public String getEmail() { return email; } public String getPhone() { return phone; } public String getRole() { return role; } public List<String> getAuthorityCodes() { return authorityCodes; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public void setName(String name) { this.name = name; } public void setEmail(String email) { this.email = email; } public void setPhone(String phone) { this.phone = phone; } public void setRole(String role) { this.role = role; } public void setAuthorities(Collection<? extends GrantedAuthority> authorities) { this.authorities = authorities; } public void setAuthorityCodes(List<String> authorityCodes) { this.authorityCodes = authorityCodes; } }
b、User.java
import java.util.Date; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * @author Administrator */ @Data @NoArgsConstructor @AllArgsConstructor public class User { private Long id; private String name; private String email; private String phone; private String gender; private Date birthday; private Integer status; private Date createDate; private Date updateDate; }
c、UserLogin.java
import java.util.Date; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * @author Administrator */ @Data @NoArgsConstructor @AllArgsConstructor public class UserLogin { private Long id; private Long userId; private String LoginName; private String phone; private String password; private Integer status; private Date createDate; private Date updateDate; }
d、Authority.java
import java.util.Date; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * @author Administrator */ @Data @NoArgsConstructor @AllArgsConstructor public class Authority { private Long id; private String code; private String introduce; private Integer status; private Date createDate; private Date updateDate; }
e、Resource.java
import java.util.Date; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * @author Administrator */ @Data @NoArgsConstructor @AllArgsConstructor public class Resource { private Long id; private String url; private String method; private String introduce; private Integer status; private Date createDate; private Date updateDate; }
5、vo包
a、UserVo.java
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * @author Administrator */ @Data @NoArgsConstructor @AllArgsConstructor public class UserVo {
private String name;
}
6、mapper包
a、UserMapper.java
import java.util.List; import org.apache.ibatis.annotations.Mapper; import com.example.model.user.CustomUserDetails; /** * @author Administrator */ @Mapper public interface UserMapper { List<CustomUserDetails> getCustomUserDetailsByUsername(String username); }
b、AuthorityMapper.java
import java.util.List; import org.apache.ibatis.annotations.Mapper; /** * @author Administrator */ @Mapper public interface AuthorityMapper { List<Long> isAnonymousAllowed(List<String> urls, String method); List<Long> checkAuthority(List<String> urls, String method, List<String> codes); }
c、ResourceMapper.java
import java.util.List; import org.apache.ibatis.annotations.Mapper; /** * @author Administrator */ @Mapper public interface ResourceMapper { List<Long> exists(String url, String method); }
7、service包
a、CustomUserDetailsService.java
import org.springframework.security.core.userdetails.UserDetailsService; /** * @author Administrator */ public interface CustomUserDetailsService extends UserDetailsService {}
b、CustomUserDetailsServiceImpl.java
import java.util.ArrayList; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; 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.UsernameNotFoundException; import org.springframework.stereotype.Service; import com.example.mapper.user.UserMapper; import com.example.model.user.CustomUserDetails; /** * @author Administrator */ @Service("customUserDetailsService") public class CustomUserDetailsServiceImpl implements CustomUserDetailsService { private UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 判断username是否为空 if (StringUtils.isBlank(username)) { throw new NullPointerException("用户名为空"); } // 自定义的CustomUserDetails所需字段 username password email phone role authorities List<CustomUserDetails> customUserDetailses = userMapper.getCustomUserDetailsByUsername(username); if (customUserDetailses.size() > 0) { CustomUserDetails customUserDetails = customUserDetailses.get(0); // 处理权限 List<String> authorityCodes = customUserDetails.getAuthorityCodes(); List<GrantedAuthority> grantedAuthorities = new ArrayList<>(); authorityCodes.stream().forEach(auth -> grantedAuthorities.add(new SimpleGrantedAuthority(auth))); String uname = customUserDetails.getName(); return new CustomUserDetails(username, customUserDetails.getPassword(), uname, customUserDetails.getEmail(), customUserDetails.getPhone(), customUserDetails.getRole(), grantedAuthorities); } return null; } @Autowired public void setUserMapper(UserMapper userMapper) { this.userMapper = userMapper; } }
c、AuthorityService.java
import java.util.List; /** * @author Administrator */ public interface AuthorityService { boolean isAnonymousAllowed(String url, String method); boolean checkAuthority(String url, String method, List<String> codes); }
d、AuthorityServiceImpl.java
import java.util.List; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.example.mapper.permission.AuthorityMapper; import com.example.util.CommonUtils; /** * @author Administrator */ @Service("authorityService") public class AuthorityServiceImpl implements AuthorityService { private AuthorityMapper authorityMapper; @Override public boolean isAnonymousAllowed(String url, String method) { if (StringUtils.isBlank(url) || StringUtils.isBlank(method)) { return false; } List<String> urls = CommonUtils.dealUrlUseWildcard(url, true); List<Long> ids = authorityMapper.isAnonymousAllowed(urls, method); return ids.size() > 0 ? true : false; } @Override public boolean checkAuthority(String url, String method, List<String> codes) { if (StringUtils.isBlank(url) || StringUtils.isBlank(method) || codes.size() == 0) { return false; } List<String> urls = CommonUtils.dealUrlUseWildcard(url, true); List<Long> ids = authorityMapper.checkAuthority(urls, method, codes); return ids.size() > 0 ? true : false; } @Autowired public void setAuthorityMapper(AuthorityMapper authorityMapper) { this.authorityMapper = authorityMapper; } }
e、ResourceService.java
/** * @author Administrator */ public interface ResourceService { boolean exists(String url, String method); }
f、ResourceServiceImpl.java
import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.example.mapper.permission.ResourceMapper; /** * @author Administrator */ @Service("resourceService") public class ResourceServiceImpl implements ResourceService { private ResourceMapper resourceMapper; @Override public boolean exists(String url, String method) { List<Long> list = resourceMapper.exists(url, method); return list.size() == 1 ? true : false; } @Autowired public void setResourceMapper(ResourceMapper resourceMapper) { this.resourceMapper = resourceMapper; } }
8、util包
a、CommonUtils.java
import java.util.ArrayList; import java.util.List; /** * @author Administrator */ public class CommonUtils { public static final String ANONYMOUS_USER = "anonymous"; /** * 将url处理成带有**通配符的 * * @param url * @param own * @return */ public static List<String> dealUrlUseWildcard(String url, boolean own) { List<String> list = new ArrayList<>(); // 添加自己 if (own) { list.add(url); } String slash = "/"; String wildcard = "/*"; if (!slash.equals(url) && !wildcard.equals(url)) { while (!slash.equals(url) && !wildcard.equals(url)) { url = urlRecursion(url); if (!wildcard.equals(url)) { list.add(url); } } } return list; } private static String urlRecursion(String url) { String wildcard = "/*"; String slash = "/"; if (url.equals(wildcard)) { return null; } // 判断url是否结尾带* else if (url.contains(wildcard)) { // url去掉/* url = url.substring(0, url.length() - 2); url = url.substring(0, url.lastIndexOf(slash)); url = url + wildcard; } else if (countStrInOther(url, slash) > 1) { url = url.substring(0, url.lastIndexOf(slash)); url = url + wildcard; } return url; } private static int countStrInOther(String source, String countStr) { int count = 0; int index = source.indexOf(countStr); while (index != -1) { count++; index = source.indexOf(countStr, index + countStr.length()); } return count; } }
b、EncryptionUtils.java
import java.security.MessageDigest; import io.micrometer.common.util.StringUtils; import lombok.extern.slf4j.Slf4j; /** * @author Administrator */ @Slf4j public class EncryptionUtils { private static final String SALT_VALUE = "hash_algorithm"; private static final String ENCRYPTION_SHA256 = "SHA-256"; private static final String ENCRYPTION_MD5 = "MD5"; private static final String DEFAULT_ID_PREFIX = "{"; private static final String DEFAULT_ID_SUFFIX = "}"; /** * SHA-2加密,安全性高于SHA-1,sha256和sha512都属于SHA-2 * * @param input * @return */ public static String sha256(String input) { try { if (StringUtils.isBlank(input)) { log.info("**********输入不能为空**********"); throw new NullPointerException("输入不能为空"); } // 添加盐值 input = input + SALT_VALUE; MessageDigest digest = MessageDigest.getInstance("SHA-256"); digest.update(input.getBytes()); byte[] md = digest.digest(); StringBuilder sb = new StringBuilder(); for (byte b : md) { sb.append(String.format("%02x", b)); } return DEFAULT_ID_PREFIX + "SHA-256" + DEFAULT_ID_SUFFIX + sb.toString(); } catch (Exception e) { System.out.println("**********sha256加密报错**********"); e.printStackTrace(); } return null; } public static String md5(String input) { try { if (StringUtils.isBlank(input)) { log.info("**********输入不能为空**********"); throw new NullPointerException("输入不能为空"); } // 添加盐值 input = input + SALT_VALUE; MessageDigest digest = MessageDigest.getInstance("MD5"); digest.update(input.getBytes()); byte[] md = digest.digest(); StringBuilder sb = new StringBuilder(); for (byte b : md) { sb.append(String.format("%02x", b)); } return DEFAULT_ID_PREFIX + "MD5" + DEFAULT_ID_SUFFIX + sb.toString(); } catch (Exception e) { System.out.println("**********md5加密报错**********"); e.printStackTrace(); } return null; } /** * 当前只有sha256和md5加密 * * @param input * @param type * @return */ public static String encryption(String input, String type) { try { if (StringUtils.isBlank(input)) { log.info("**********输入不能为空**********"); throw new NullPointerException("输入不能为空"); } if (StringUtils.isBlank(type) || !ENCRYPTION_SHA256.equals(type) || !ENCRYPTION_MD5.equals(type)) { type = ENCRYPTION_SHA256; } // 添加盐值 input = input + SALT_VALUE; MessageDigest digest = MessageDigest.getInstance(type); digest.update(input.getBytes()); byte[] md = digest.digest(); StringBuilder sb = new StringBuilder(); for (byte b : md) { sb.append(String.format("%02x", b)); } return sb.toString(); } catch (Exception e) { System.out.println("**********" + type + "加密报错**********"); e.printStackTrace(); } return null; } }
c、JwtUtils.java
import java.time.Instant; import java.util.Date; import java.util.Map; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import com.alibaba.fastjson2.JSON; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTCreator; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.InvalidClaimException; import com.auth0.jwt.exceptions.TokenExpiredException; import com.example.config.JwtAuthentication; import io.micrometer.common.util.StringUtils; import lombok.extern.slf4j.Slf4j; /** * @author Administrator */ @Slf4j public class JwtUtils { public static final String secret = "project"; public static final Integer EXP_TIME = 60 * 60 * 24 * 30; public static final String TOKEN_NAME = "sec_tk"; public static final String USER_AGENT = "User-Agent"; /** * 根据验证和时间生成token * * @param authentication 验证对象 * @param expTime 过期时间 * @return */ public static String genToken(Authentication authentication, Integer expTime, String userAgent) { if (expTime == null) { expTime = EXP_TIME; } // 数据精确到毫秒 Date expDate = new Date(System.currentTimeMillis() + expTime * 1000L); String authenticationJson = JSON.toJSONString(authentication); return JWT.create() // 配置过期时间 .withExpiresAt(expDate).withSubject(TOKEN_NAME) // 设置接收方信息,登录用户信息 .withClaim(USER_AGENT, userAgent).withAudience(authenticationJson) // 签证信息 .sign(Algorithm.HMAC256(secret)); } /** * 生成JWT token * * @param authentication * @return */ public static String genToken(Authentication authentication, String userAgent) { return genToken(authentication, EXP_TIME, userAgent); } /** * 根据map创建token * * @param map 需要创建token的信息,保存在withAudience中 * @param expTime 过期时间 * @return */ public static String genToken(Map<String, String> map, Integer expTime) { if (expTime == null) { expTime = EXP_TIME; } JWTCreator.Builder builder = JWT.create(); // 设置过期时间 builder.withExpiresAt(new Date(System.currentTimeMillis() + expTime * 1000L)); map.forEach((key, value) -> { if (StringUtils.isNotBlank(key) && StringUtils.isNotBlank(value)) { builder.withClaim(key, value); } }); return builder.sign(Algorithm.HMAC256(secret)); } public static String genToken(Map<String, String> map) { return genToken(map, EXP_TIME); } public static void tokenVerify(String token, String userAgent) { JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(secret)).build(); jwtVerifier.verify(token); Date expiresAt = JWT.decode(token).getExpiresAt(); // 判断时间是否过期 if (!expiresAt.after(new Date())) { throw new TokenExpiredException("token已过期", Instant.now()); } if (userAgent == null || !userAgent.equals(JWT.decode(token).getClaim(USER_AGENT).asString())) { throw new InvalidClaimException("token非法传递"); } String json = JWT.decode(token).getAudience().get(0); JwtAuthentication authentication = JSON.parseObject(json, JwtAuthentication.class); SecurityContextHolder.getContext().setAuthentication(authentication); } }
9、filter包
a、JwtAuthenticationTokenFilter.java
import java.io.IOException; import org.springframework.web.filter.OncePerRequestFilter; import com.alibaba.fastjson2.JSONObject; import com.example.config.ResponseVo; import com.example.util.JwtUtils; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; /** * @author Administrator */ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { if (JwtUtils.TOKEN_NAME.equals(cookie.getName())) { try { JwtUtils.tokenVerify(cookie.getValue(), request.getHeader(JwtUtils.USER_AGENT)); } catch (Exception e) { e.printStackTrace(); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(JSONObject .toJSONString(new ResponseVo<>(HttpServletResponse.SC_OK, "非法token", e.getMessage()))); return; } } } } filterChain.doFilter(request, response); } }
b、LoginFilter.java
import java.io.BufferedReader; import java.io.IOException; import java.util.HashMap; import java.util.Map; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import com.alibaba.fastjson2.JSON; import io.micrometer.common.util.StringUtils; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.SneakyThrows; /** * @author Administrator */ public class LoginFilter extends UsernamePasswordAuthenticationFilter { private final String method = "POST"; @SneakyThrows @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (!method.equals(request.getMethod())) { throw new AuthenticationServiceException("请求方法错误,请求方法应为POST,当前请求方法是:" + request.getMethod()); } Map<String, String> user = obtainUser(request); UsernamePasswordAuthenticationToken authentication = UsernamePasswordAuthenticationToken.unauthenticated(user.get("username"), user.get("password")); // 此处可能报错,AuthenticationManager可能为空 return this.getAuthenticationManager().authenticate(authentication); } private Map<String, String> obtainUser(HttpServletRequest request) throws IOException { Map<String, String> user = new HashMap<>(2); String username = request.getParameter("username"); String password = request.getParameter("password"); if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) { BufferedReader reader = request.getReader(); StringBuffer sb = new StringBuffer(); String line = null; if ((line = reader.readLine()) != null) { sb.append(line); } reader.close(); Map<String, String> map = JSON.parseObject(sb.toString(), Map.class); username = map.get("username"); password = map.get("password"); if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) { throw new AuthenticationServiceException("用户或密码为空"); } } user.put("username", username); user.put("password", password); return user; } }
10、handler包
a、LoginSuccessHandler.java
import java.io.IOException; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import com.alibaba.fastjson2.JSONObject; import com.example.config.ResponseVo; import com.example.model.user.CustomUserDetails; import com.example.util.JwtUtils; import com.example.vo.UserVo; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; /** * @author Administrator */ @Component public class LoginSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { // 添加cookie Cookie cookie = new Cookie(JwtUtils.TOKEN_NAME, JwtUtils.genToken(authentication, request.getHeader(JwtUtils.USER_AGENT))); cookie.setMaxAge(JwtUtils.EXP_TIME); response.addCookie(cookie); // 获取用户对象 CustomUserDetails customUserDetails = (CustomUserDetails)authentication.getPrincipal(); response.setContentType("application/json;charset=UTF-8"); ResponseVo<UserVo> responseVo = new ResponseVo<>(HttpServletResponse.SC_OK, new UserVo(customUserDetails.getName()), "登陆成功"); response.getWriter().write(JSONObject.toJSONString(responseVo)); } }
b、LoginFailureHandler.java
import java.io.IOException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.stereotype.Component; import com.alibaba.fastjson2.JSONObject; import com.example.config.ResponseVo; import com.example.vo.UserVo; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; /** * @author Administrator */ @Component public class LoginFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException { response.setContentType("application/json;charset=UTF-8"); ResponseVo<UserVo> responseVo = new ResponseVo<>(HttpServletResponse.SC_UNAUTHORIZED, new UserVo(), "用户名或密码错误"); response.getWriter().write(JSONObject.toJSONString(responseVo)); } }
c、CustomAccessDeniedHandler.java
import java.io.IOException; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.stereotype.Component; import com.alibaba.fastjson2.JSONObject; import com.example.config.ResponseVo; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; /** * @author Administrator */ @Component public class CustomAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException { response.setContentType("application/json;charset=UTF-8"); ResponseVo<String> responseVo = new ResponseVo<>(HttpServletResponse.SC_FORBIDDEN, "权限不足", "授权失败"); response.getWriter().write(JSONObject.toJSONString(responseVo)); } }
11、config包
a、CorsConfig.java
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @author Administrator */ @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") // 是否发送cookie .allowCredentials(true).allowedOriginPatterns("*") .allowedMethods(new String[] {"GET", "POST", "PUT", "DELETE", "OPTION"}).allowedHeaders("*") .exposedHeaders("*"); } }
b、CustomAuthenticationEntryPoint.java
import java.io.IOException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; import com.alibaba.fastjson2.JSONObject; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; /** * @author Administrator */ @Component public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { response.setContentType("application/json;charset=utf-8"); ResponseVo<String> responseVo = new ResponseVo<>(HttpServletResponse.SC_FORBIDDEN, "权限不足", "认证失败"); response.getWriter().write(JSONObject.toJSONString(responseVo)); } }
c、CustomAuthorizationManager.java
import java.util.Collection; import java.util.List; import java.util.function.Supplier; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.web.access.intercept.RequestAuthorizationContext; import org.springframework.stereotype.Component; import com.example.service.permission.AuthorityService; import com.example.service.permission.ResourceService; import jakarta.servlet.http.HttpServletRequest; /** * @author Administrator */ @Component public class CustomAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> { private ResourceService resourceService; private AuthorityService authorityService; @Override public void verify(Supplier<Authentication> authentication, RequestAuthorizationContext request) { AuthorizationManager.super.verify(authentication, request); } @Override public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext request) { return new AuthorizationDecision(authorize(authentication, request).isGranted()); } @Override public AuthorizationResult authorize(Supplier<Authentication> authentication, RequestAuthorizationContext request) { HttpServletRequest req = request.getRequest(); String url = req.getRequestURI(); String method = req.getMethod(); // 判断是否存在对应的resource boolean exists = resourceService.exists(url, method); if (!exists) { return new AuthorizationDecision(false); } // 匿名用户检查 if (authentication == null || authentication.get().getName() == null) { return new AuthorizationDecision(authorityService.isAnonymousAllowed(url, method)); } Collection<? extends GrantedAuthority> authorities = authentication.get().getAuthorities(); // UserDetailsService创建的user的codes List<String> list = authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()); // 其他用户检查 return new AuthorizationDecision(authorityService.checkAuthority(url, method, list)); } @Autowired public void setResourceService(ResourceService resourceService) { this.resourceService = resourceService; } @Autowired public void setAuthorityService(AuthorityService authorityService) { this.authorityService = authorityService; } }
d、JwtAuthentication.java
import java.security.Principal; import java.util.Collection; import org.springframework.security.core.AuthenticatedPrincipal; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import com.example.model.user.CustomUserDetails; /** * @author Administrator */ public class JwtAuthentication implements Authentication { private Collection<SimpleGrantedAuthority> authorities; private Object details; private boolean authenticated; private CustomUserDetails principal; private Object credentials; @Override public Collection<SimpleGrantedAuthority> getAuthorities() { return authorities; } public void setAuthorities(Collection<SimpleGrantedAuthority> authorities) { this.authorities = authorities; } @Override public Object getDetails() { return details; } public void setDetails(Object details) { this.details = details; } @Override public boolean isAuthenticated() { return authenticated; } @Override public void setAuthenticated(boolean authenticated) { this.authenticated = authenticated; } @Override public Object getPrincipal() { return principal; } public void setPrincipal(CustomUserDetails principal) { this.principal = principal; } @Override public Object getCredentials() { return credentials; } public void setCredentials(Object credentials) { this.credentials = credentials; } @Override public String getName() { if (this.getPrincipal()instanceof UserDetails userDetails) { return userDetails.getUsername(); } if (this.getPrincipal()instanceof AuthenticatedPrincipal authenticatedPrincipal) { return authenticatedPrincipal.getName(); } if (this.getPrincipal()instanceof Principal principal) { return principal.getName(); } return (this.getPrincipal() == null) ? "" : this.getPrincipal().toString(); } }
e、ResponseVo.java
import java.io.Serializable; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * @author Administrator */ @Data @NoArgsConstructor @AllArgsConstructor @JsonInclude(JsonInclude.Include.NON_EMPTY) public class ResponseVo<T> implements Serializable { private int code; private T data; private String msg; }
f、SecurityConfig.java
import java.util.Arrays; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; 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.CsrfConfigurer; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import com.example.filter.JwtAuthenticationTokenFilter; import com.example.filter.LoginFilter; import com.example.handler.CustomAccessDeniedHandler; import com.example.handler.LoginFailureHandler; import com.example.handler.LoginSuccessHandler; import com.example.util.EncryptionUtils; import lombok.extern.slf4j.Slf4j; /** * @author Administrator */ @Configuration @EnableWebSecurity @Slf4j public class SecurityConfig { private AuthenticationConfiguration authenticationConfiguration; private CustomAuthorizationManager customAuthorizationManager; private CustomAuthenticationEntryPoint customAuthenticationEntryPoint; private CustomAccessDeniedHandler customAccessDeniedHandler; private LoginSuccessHandler loginSuccessHandler; private LoginFailureHandler loginFailureHandler; /** * cors: 跨域配置 csrf:禁用跨站请求伪造,使用jwt permitAll:公开的api无需认证 anyRequest().authenticated():其他所有请求都需要认证 * * @param http 配置 HTTP 安全策略的类 * @return SecurityFilterChain 处理 HTTP 请求的核心组件 * @throws Exception 异常 */ @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http.cors(cors -> cors.configurationSource(corsConfigurationSource())).csrf(CsrfConfigurer::disable) .authorizeHttpRequests(auth -> auth.anyRequest().access(customAuthorizationManager)) .exceptionHandling(eh -> eh.authenticationEntryPoint(customAuthenticationEntryPoint) .accessDeniedHandler(customAccessDeniedHandler)) .addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class) .addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class).build(); } @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowCredentials(true); configuration.setAllowedHeaders(Arrays.asList("*")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTION")); configuration.setAllowedOriginPatterns(Arrays.asList("*")); configuration.setMaxAge(3600L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; } @Bean public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() { return new JwtAuthenticationTokenFilter(); } @Bean public PasswordEncoder passwordEncoder() { return new PasswordEncoder() { @Override public String encode(CharSequence rawPassword) { if (rawPassword == null) { throw new IllegalArgumentException("rawPassword cannot be null"); } return EncryptionUtils.sha256(rawPassword.toString()); } @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { if (encodedPassword == null || encodedPassword.length() == 0) { log.info("Empty encoded password"); return false; } return encode(rawPassword).equals(encodedPassword); } }; } /** * 启动注入会调用 * * @return LoginFilter 登录过滤器 * @throws Exception 抛出异常 */ @Bean public LoginFilter loginFilter() throws Exception { LoginFilter loginFilter = new LoginFilter(); loginFilter.setAuthenticationManager(authenticationConfiguration.getAuthenticationManager()); loginFilter.setAuthenticationSuccessHandler(loginSuccessHandler); loginFilter.setAuthenticationFailureHandler(loginFailureHandler); return loginFilter; } @Autowired public void setAuthenticationConfiguration(AuthenticationConfiguration authenticationConfiguration) { this.authenticationConfiguration = authenticationConfiguration; } @Autowired public void setCustomAuthorizationManager(CustomAuthorizationManager customAuthorizationManager) { this.customAuthorizationManager = customAuthorizationManager; } @Autowired public void setCustomAuthenticationEntryPoint(CustomAuthenticationEntryPoint customAuthenticationEntryPoint) { this.customAuthenticationEntryPoint = customAuthenticationEntryPoint; } @Autowired public void setCustomAccessDeniedHandler(CustomAccessDeniedHandler customAccessDeniedHandler) { this.customAccessDeniedHandler = customAccessDeniedHandler; } @Autowired public void setLoginSuccessHandler(LoginSuccessHandler loginSuccessHandler) { this.loginSuccessHandler = loginSuccessHandler; } @Autowired public void setLoginFailureHandler(LoginFailureHandler loginFailureHandler) { this.loginFailureHandler = loginFailureHandler; } }
12、controller包
a、IndexController.java
import java.util.HashMap; import java.util.Map; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import com.alibaba.fastjson2.JSONObject; import com.example.config.JwtAuthentication; import com.example.config.ResponseVo; import com.example.model.user.CustomUserDetails; import com.example.vo.UserVo; import jakarta.servlet.http.HttpServletResponse; /** * @author Administrator */ @RestController public class IndexController { @GetMapping("/user/info") public ResponseVo<UserVo> userInfo() { ResponseVo<UserVo> responseVo = new ResponseVo<>(); JwtAuthentication authentication = (JwtAuthentication)SecurityContextHolder.getContext().getAuthentication(); CustomUserDetails customUserDetails = (CustomUserDetails)authentication.getPrincipal(); UserVo userVo = new UserVo(customUserDetails.getName()); responseVo.setCode(HttpServletResponse.SC_OK); responseVo.setData(userVo); responseVo.setMsg("获取数据成功"); return responseVo; } @GetMapping("/admin/api") public String adminApi() { Map<String, String> map = new HashMap<>(1); map.put("msg", "ok"); return JSONObject.toJSONString(map); } @GetMapping("/ordinary/api") public String ordinaryApi() { Map<String, String> map = new HashMap<>(1); map.put("msg", "ok"); return JSONObject.toJSONString(map); } @GetMapping("/anonymous/api") public String anonymousApi() { Map<String, String> map = new HashMap<>(1); map.put("msg", "ok"); return JSONObject.toJSONString(map); } }
三、Vue
1、main.ts
import {createApp} from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
import router from '@/router/index'
const app = createApp(App)
app.use(router)
const pinia = createPinia()
app.use(pinia)
app.mount('#app')
2、router/index.ts
import { createRouter, createWebHistory } from "vue-router"
export default createRouter({
    history: createWebHistory(),
    routes: [
        {
            path: '/',
            component: () => import('@/components/Swiper.vue')
        },
        {
            name: 'home',
            path: '/home',
            component: () => import('@/components/Swiper.vue')
        },
        {
            name: 'table',
            path: '/table',
            component: () => import('@/views/Table.vue')
        },
        {
            name: 'login',
            path: '/login',
            component: () => import('@/views/Login.vue')
        },
        {
            name: 'register',
            path: '/register',
            component: () => import('@/views/Register.vue')
        },
        {
            name: 'adminApi',
            path: '/adminPath',
            component: () => import('@/views/admin/Api.vue')
        },
        {
            name: 'userQuery',
            path: '/userQuery',
            component: () => import('@/views/person/Query.vue')
        },
        {
            name: 'userTest',
            path: '/userTest',
            component: () => import('@/views/anonymity/Person.vue')
        }
    ]
})
3、store/user.ts
import { defineStore } from "pinia"
import { ref } from 'vue'
export const useUserStore = defineStore('user', () => {
    // 用户名
    let username = ref('')
    function updateUserName(name: string) {
        username.value = name
    }
    return {
        username,
        updateUserName
    }
})
4、Top.vue
<template>
    <div class="top">
        <div v-if="username">
            <h2>{{ username }}</h2>
            <button @click="logout">退出</button>
        </div>
        <div v-else class="user">
            <RouterLink :key="1" :to="{name: 'login'}" class="login">登录</RouterLink>
            <RouterLink :key="2" :to="{name: 'register'}" class="register">注册</RouterLink>
        </div>
    </div>
</template>
<script lang="ts" setup name="Top">
    import { onMounted } from 'vue'
    import { RouterLink } from 'vue-router'
    import { useUserStore } from '@/store/user'
    import { storeToRefs } from 'pinia'
    import axios from 'axios'
    let userStore = useUserStore()
    let { username } = storeToRefs(userStore)
    onMounted(async() => {
        //加载用户信息
        const appiClient = axios.create({
            baseURL: 'http://localhost',
            withCredentials: true
        })
        let {data} = await appiClient.get(
            '/user/info'
        )
        if (data.code == 200) {
            userStore.updateUserName(data.data.name)
        } else {
            console.info(data)
        }
    })
    function logout() {
        userStore.updateUserName('')
    }
</script>
<style scoped>
    .top {
        display: flex;
        width: 99vw;
        height: 17vh;
        background-color: aqua;
        justify-content: center;
        align-items: center;
        border: 2px #000 solid;
    }
    .top .user {
        width: 99vw;
        height: 17vh;
        display: flex;
        justify-content: end;
        align-items: center;
    }
    .top .user .login {
        padding: 5px 20px;
        border-radius: 5px;
        background-color: greenyellow;
        margin-right: 24px;
        text-decoration: none;
        color: green;
    }
    .top .user .login:hover {
        background-color: green;
        color: greenyellow;
        font-weight: bolder;
    }
    .top .user .register {
        padding: 5px 20px;
        border-radius: 5px;
        background-color: green;
        margin-right: 24px;
        text-decoration: none;
        color: greenyellow;
    }
    .top .user .register:hover {
        background-color: greenyellow;
        color: green;
        font-weight: bolder;
    }
</style>
5、Login.vue
<template>
    <div class="login">
        <div class="login-box">
            <h2>用户登录</h2>
            <p class="login-input">
                <span>用户名:</span>
                <input type="text" name="username" class="username" v-model="username"/>
            </p>
            <p class="login-input">
                <span>密码:</span>
                <input type="text" name="password" class="password" v-model="password"/>
            </p>
            <p class="login-button">
                <input type="button" @click="login" value="登录">
            </p>
            <p v-if="msg">{{ msg }}</p>
        </div>
    </div>
</template>
<script lang="ts" setup name="Login">
    import axios from 'axios'
    import { ref } from 'vue'
    import { useUserStore } from '@/store/user'
    let username = ref('')
    let password = ref('')
    let msg = ref('')
    let userStore = useUserStore()
    async function login() {
        const appiClient = axios.create({
            baseURL: 'http://localhost',
            withCredentials: true
        })
        let {data} = await appiClient.post(
            '/login',
            {username: username.value, password: password.value},
            {headers: {"Content-Type": "application/json"}}
        )
        console.info(data.data.name)
        if (data.code == 200) {
            userStore.updateUserName(data.data.name)
        } else {
            msg = data.msg
        }
    }
</script>
<style scoped lang="scss">
    .login {
        height: 80vh;
        display: flex;
        justify-content: center;
        align-items: center;
        align-content: center;
        background-color: skyblue;
        background-image: url('@/assets/img/login-bgi.png');
        opacity: 0.9;
        .login-box {
            border: 1px solid black;
            width: 400px;
            height: 230px;
            border-radius: 5%;
            background-color: skyblue;
            box-shadow: 5px 5px 5px #473fe4;
            h2 {
                text-align: center;
            }
            .login-input {
                text-align: end;
                margin-right: 20px;
                .username {
                    width: 240px;
                    height: 24px;
                    border-radius: 5px;
                    padding: 3px;
                    margin: 0 10px;
                    font-size: 20px;
                    margin-right: 20px;
                }
                .password {
                    width: 240px;
                    height: 24px;
                    border-radius: 5px;
                    padding: 3px;
                    margin: 0 10px;
                    font-size: 20px;
                    margin-right: 20px;
                }
            }
            .login-button {
                text-align: center;
                input[type='button'] {
                    padding: 5px 20px;
                    border-radius: 5px;
                    background-color: aquamarine;
                }
                input[type='button']:hover {
                    background-color: aqua;
                    font-weight: bolder;
                }
            }
        }
    }
</style>
 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号