SpringSecurity
它是一个身份认证及权限控制的框架。shiro是它的对标框架,springsecurity已经开发很多年了,由于shiro的优势,它发展的不是很好,直到springboot出现,security的配置变的非常简单,现在微服务的项目都会优先选择security框架。security实现原理是通过Filter实现的。
代码分布截图
注意:导包导的user要是自己的,不能导依赖里面自带的user包。
1、基础代码
Result
import lombok.Data; import java.util.HashMap; import java.util.Map; import static com.wztsl.springboo.contants.ResultCode.ERROR; import static com.wztsl.springboo.contants.ResultCode.SUCCESS; /** * @author szsw * @date 2023/2/3 18:18:15 */ @Data public class Result { private Integer code; private String message; private Map<String, Object> map = new HashMap<>(); private Result() { } public static Result ok() { Result r = new Result(); r.setCode(SUCCESS.getCode()); r.setMessage(SUCCESS.getMessage()); return r; } public static Result error() { Result r = new Result(); r.setCode(ERROR.getCode()); r.setMessage(ERROR.getMessage()); return r; } public Result put(String key, Object value) { map.put(key, value); return this; } public Object get(String key) { return map.get(key); } }
ResultCode
/** * @author szsw * @date 2023/2/3 18:26:03 */ public enum ResultCode { /** * */ SUCCESS(0, "请求成功"), ERROR(1, "请求失败"), ; private int code; private String message; ResultCode(int code, String message) { this.code = code; this.message = message; } public int getCode() { return code; } public String getMessage() { return message; } }
StringConstant
/** * @author szsw * @date 2023/2/3 19:42:55 */ public class StringConstant { /** * session当前登录人信息 */ public static final String SESSION_USER = "SESSION_USER"; public static final String AUTH_LIST = "AUTH_LIST"; public static final String TOKEN = "token"; private StringConstant() { } }
AuthenticationEnum
/** * @author szsw * @date 2023/2/3 19:43:10 */ public enum AuthenticationEnum { }
2、工具代码
Md5
import lombok.extern.slf4j.Slf4j; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @Slf4j @SuppressWarnings("java:S112") public final class Md5 { private Md5() { } public static String encrypt(String strSrc) { try { char[] hexChars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; byte[] bytes = strSrc.getBytes(); MessageDigest md = MessageDigest.getInstance("Md5"); md.update(bytes); bytes = md.digest(); int j = bytes.length; char[] chars = new char[j * 2]; int k = 0; int i; for (i = 0; i < bytes.length; i++) { byte b = bytes[i]; chars[k++] = hexChars[b >>> 4 & 0xf]; chars[k++] = hexChars[b & 0xf]; } return new String(chars); } catch (NoSuchAlgorithmException e) { log.info("MD5计算异常!", e); throw new RuntimeException("MD5加密出错!!+" + e); } } }
ResponseUtil
import com.fasterxml.jackson.databind.ObjectMapper; import com.wztsl.springboo.vo.Result; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 响应工具 * * @author by szsw */ @Slf4j public class ResponseUtil { private ResponseUtil() { } public static void out(HttpServletResponse response, Result r) { ObjectMapper mapper = new ObjectMapper(); response.setStatus(HttpStatus.OK.value()); response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); try { mapper.writeValue(response.getOutputStream(), r); } catch (IOException e) { log.info("响应IO异常!", e); } } }
TokenManager
import io.jsonwebtoken.CompressionCodecs; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.stereotype.Component; import java.util.Date; /** * token管理 * * @author by szsw */ @Component public class TokenManager { private static final long TOKEN_EXPIRATION = 24 * 60 * 60 * 1000L; private String tokenSignKey = "123456"; /** * 生成token * * @param username 用户名 * @return 通过用户名生成的token */ public String createToken(String username) { return Jwts.builder().setSubject(username) .setExpiration(new Date(System.currentTimeMillis() + TOKEN_EXPIRATION)) .signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact(); } /** * 通过token获取用户名 * * @param token 令牌 * @return 令牌对应的用户名 */ public String getUserFromToken(String token) { return Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject(); } @SuppressWarnings("all") public void removeToken(String token) { //jwttoken无需删除,客户端扔掉即可。 } }
3、实体代码
User
import lombok.Data; /** * @author szsw * @date 2023/2/3 20:11:37 */ @Data public class User { private String username; private String password; }
SecurityUser
import lombok.Data; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * @author szsw * @date 2023/2/3 20:11:45 */ @Data public class SecurityUser implements UserDetails { private User user; private List<String> authList; public SecurityUser(User user) { this.user = user; } /** * 返回用户的权限 * * @return the authorities, sorted by natural key (never <code>null</code>) */ @Override public Collection<? extends GrantedAuthority> getAuthorities() { Collection<GrantedAuthority> authorities = new ArrayList<>(); if (CollectionUtils.isEmpty(authList)) { return authorities; } for (String auths : authList) { if (StringUtils.isEmpty(auths)) { continue; } authorities.add(new SimpleGrantedAuthority(auths)); } return authorities; } /** * Returns the password used to authenticate the user. * * @return the password */ @Override public String getPassword() { return user.getPassword(); } /** * Returns the username used to authenticate the user. Cannot return <code>null</code>. * * @return the username (never <code>null</code>) */ @Override public String getUsername() { return user.getUsername(); } /** * Indicates whether the user's account has expired. An expired account cannot be * authenticated. * * @return <code>true</code> if the user's account is valid (ie non-expired), * <code>false</code> if no longer valid (ie expired) */ @Override public boolean isAccountNonExpired() { return true; } /** * Indicates whether the user is locked or unlocked. A locked user cannot be * authenticated. * * @return <code>true</code> if the user is not locked, <code>false</code> otherwise */ @Override public boolean isAccountNonLocked() { return true; } /** * Indicates whether the user's credentials (password) has expired. Expired * credentials prevent authentication. * * @return <code>true</code> if the user's credentials are valid (ie non-expired), * <code>false</code> if no longer valid (ie expired) */ @Override public boolean isCredentialsNonExpired() { return true; } /** * Indicates whether the user is enabled or disabled. A disabled user cannot be * authenticated. * * @return <code>true</code> if the user is enabled, <code>false</code> otherwise */ @Override public boolean isEnabled() { return true; } }
4、配置代码
TokenWebSecurityConfig
import com.wztsl.springboo.security.filter.TokenAuthenticationFilter; import com.wztsl.springboo.security.filter.TokenLoginFilter; import com.wztsl.springboo.security.filter.TokenLogoutHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; /** * Security配置类 * * @author by szsw * @date 2020/10/27 16:39 */ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter { private UserDetailsService userDetailsService; private TokenManager tokenManager; private DefaultPasswordEncoder defaultPasswordEncoder; @Autowired public TokenWebSecurityConfig(UserDetailsService userDetailsService, DefaultPasswordEncoder defaultPasswordEncoder, TokenManager tokenManager) { this.userDetailsService = userDetailsService; this.defaultPasswordEncoder = defaultPasswordEncoder; this.tokenManager = tokenManager; } /** * 配置设置 * * @param http httpSecurity配置对象 * @throws Exception 异常 */ @Override protected void configure(HttpSecurity http) throws Exception { /* * always – 如果session不存在总是需要创建; * ifRequired – 仅当需要时,创建session(默认配置); * never – 框架从不创建session,但如果已经存在,会使用该session ; * stateless – Spring Security不会创建session,或使用session; * 解决Feign调用时session丢失问题 */ http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER); /* * "migrateSession",即认证时,创建一个新http session,原session失效,属性从原session中拷贝过来 * “none”,原session保持有效; * “newSession”,新创建session,且不从原session中拷贝任何属性。 * 解决Feign调用时session丢失问题 */ http.sessionManagement().sessionFixation().none(); http.exceptionHandling() .authenticationEntryPoint(new UnauthorizedEntryPoint()) .and() .cors() .and() .csrf() .disable() .authorizeRequests() .anyRequest() .authenticated() .and() .logout() .logoutUrl("/center/logout") .addLogoutHandler(new TokenLogoutHandler(tokenManager)) .and() .addFilter(new TokenLoginFilter(authenticationManager(), tokenManager)) .addFilter(new TokenAuthenticationFilter(authenticationManager(), tokenManager)) .httpBasic(); } /** * 密码处理 * * @param auth 权限管理创建者 * @throws Exception 异常 */ @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder); } /** * 配置哪些请求不拦截 * * @param web webSecurity配置对象 * @throws Exception 异常 */ @Override public void configure(WebSecurity web) throws Exception { String[] matchers = {"/**/allowAuth/**", "/assets/**"}; web.ignoring().antMatchers(matchers); } }
DefaultPasswordEncoder
import com.wztsl.springboo.utils.Md5; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; /** * @author szsw * @date 2023/2/3 19:46:52 */ @Component public class DefaultPasswordEncoder implements PasswordEncoder { /** * Encode the raw password. Generally, a good encoding algorithm applies a SHA-1 or * greater hash combined with an 8-byte or greater randomly generated salt. * * @param rawPassword 密码 */ @Override public String encode(CharSequence rawPassword) { return Md5.encrypt(rawPassword.toString()); } /** * Verify the encoded password obtained from storage matches the submitted raw * password after it too is encoded. Returns true if the passwords match, false if * they do not. The stored password itself is never decoded. * * @param rawPassword the raw password to encode and match * @param encodedPassword the encoded password from storage to compare with * @return true if the raw password, after encoding, matches the encoded password from * storage */ @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { if(rawPassword == null || encodedPassword == null){ return false; } return encodedPassword.equals(Md5.encrypt(rawPassword.toString())); } }
UnauthorizedEntryPoint
import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author szsw * @date 2023/2/3 19:47:06 */ public class UnauthorizedEntryPoint implements AuthenticationEntryPoint { /** * Commences an authentication scheme. * <p> * <code>ExceptionTranslationFilter</code> will populate the <code>HttpSession</code> * attribute named * <code>AbstractAuthenticationProcessingFilter.SPRING_SECURITY_SAVED_REQUEST_KEY</code> * with the requested target URL before calling this method. * <p> * Implementations should modify the headers on the <code>ServletResponse</code> as * necessary to commence the authentication process. * * @param request that resulted in an <code>AuthenticationException</code> * @param response so that the user agent can begin authentication * @param authException that caused the invocation */ @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { } }
5、Filter代码
TokenAuthenticationFilter
import com.wztsl.springboo.utils.ResponseUtil; import com.wztsl.springboo.utils.StringUtils; import com.wztsl.springboo.utils.TokenManager; import com.wztsl.springboo.vo.Result; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.util.Collection; import java.util.List; import static com.wztsl.springboo.contants.StringConstant.*; /** * @author szsw * @date 2023/2/3 19:47:36 */ public class TokenAuthenticationFilter extends BasicAuthenticationFilter { private AuthenticationManager authenticationManager; private TokenManager tokenManager; public TokenAuthenticationFilter(AuthenticationManager authenticationManager, TokenManager tokenManager) { super(authenticationManager); this.authenticationManager = authenticationManager; this.tokenManager = tokenManager; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { String requestUri = request.getRequestURI(); handing(request, response, chain, requestUri); } @SuppressWarnings("unchecked") private void handing(HttpServletRequest request, HttpServletResponse response, FilterChain chain, String requestUri) throws IOException, ServletException { HttpSession session = request.getSession(); Object userObj = session.getAttribute(SESSION_USER); if (userObj != null) { UsernamePasswordAuthenticationToken auth = createUpat(request); if(auth == null){ ResponseUtil.out(response, Result.error("没有权限!")); } SecurityContextHolder.getContext().setAuthentication(auth); List<SimpleGrantedAuthority> authorityList = (List<SimpleGrantedAuthority>) session.getAttribute(AUTH_LIST); boolean authFlag = authorityList.stream().anyMatch(t -> requestUri.contains(t.getAuthority())); if (authFlag) { chain.doFilter(request, response); }else{ ResponseUtil.out(response, Result.error("没有权限!")); } } else { ResponseUtil.out(response, Result.error("请登录!")); } } @SuppressWarnings("unchecked") private UsernamePasswordAuthenticationToken createUpat(HttpServletRequest request) { String token = request.getHeader(TOKEN); if (StringUtils.isEmpty(token)) { return null; } String username = tokenManager.getUserFromToken(token); if (StringUtils.isEmpty(username)) { return null; } Object authList = request.getSession().getAttribute(AUTH_LIST); return new UsernamePasswordAuthenticationToken(username, token, (Collection<? extends GrantedAuthority>) authList); } }
TokenLoginFilter
import com.fasterxml.jackson.databind.ObjectMapper; import com.wztsl.springboo.utils.ResponseUtil; import com.wztsl.springboo.utils.TokenManager; import com.wztsl.springboo.vo.Result; import com.wztsl.springboo.vo.SecurityUser; import com.wztsl.springboo.vo.User; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; 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.ArrayList; import static com.wztsl.springboo.contants.StringConstant.AUTH_LIST; import static com.wztsl.springboo.contants.StringConstant.SESSION_USER; /** * @author szsw * @date 2023/2/3 19:47:46 */ public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter { private AuthenticationManager authenticationManager; private TokenManager tokenManager; public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager) { this.authenticationManager = authenticationManager; this.tokenManager = tokenManager; this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login")); } /** * 获取数据和返回响应的 * * @param request 请求 * @param response 响应 * @return 认证 * @throws AuthenticationException 认证过程中可能会由Spring抛出认证异常 */ @Override @SuppressWarnings("java:S112") public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { try { User user = new ObjectMapper().readValue(request.getInputStream(), User.class); UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()); return authenticationManager.authenticate(authToken); } catch (IOException e) { throw new RuntimeException("认证失败!"); } } /** * 认证成功 * * @param request 请求 * @param response 响应 * @param chain chain对象 * @param authResult the object returned from the <tt>attemptAuthentication</tt> * method. * @throws IOException 认证过程中可能会由Spring抛出认证异常 * @throws ServletException 认证过程中可能会由Spring抛出认证异常 */ @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { SecurityUser securityUser = (SecurityUser) authResult.getPrincipal(); String token = tokenManager.createToken(securityUser.getUsername()); request.getSession().setAttribute(SESSION_USER, securityUser.getUser()); request.getSession().setAttribute(AUTH_LIST, securityUser.getAuthorities()); ResponseUtil.out(response, Result.ok().put("token", token)); } /** * 认证失败 * * @param request 请求 * @param response 响应 * @param e 认证异常 */ @Override protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { ResponseUtil.out(response, Result.error("用户名或密码错误!")); } }
TokenLogoutHandler
import com.wztsl.springboo.utils.TokenManager; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.logout.LogoutHandler; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author szsw * @date 2023/2/3 19:47:54 */ public class TokenLogoutHandler implements LogoutHandler { private TokenManager tokenManager; public TokenLogoutHandler(TokenManager tokenManager) { this.tokenManager = tokenManager; } /** * Causes a logout to be completed. The method must complete successfully. * * @param request the HTTP request * @param response the HTTP response * @param authentication the current principal details */ @Override public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { } }
6、service代码
UserDetailsServiceImpl
import com.wztsl.springboo.utils.Md5; import com.wztsl.springboo.vo.SecurityUser; import com.wztsl.springboo.vo.User; 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.ArrayList; import java.util.List; /** * @author szsw * @date 2023/2/3 19:48:10 */ @Service public class UserDetailsServiceImpl implements UserDetailsService { /** * Locates the user based on the username. In the actual implementation, the search * may possibly be case sensitive, or case insensitive depending on how the * implementation instance is configured. In this case, the <code>UserDetails</code> * object that comes back may have a username that is of a different case than what * was actually requested.. * * @param username the username identifying the user whose data is required. * @return a fully populated user record (never <code>null</code>) * @throws UsernameNotFoundException if the user could not be found or the user has no * GrantedAuthority */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = new User(); SecurityUser securityUser = new SecurityUser(user); if ("admin".equals(username)) { user.setPassword(Md5.encrypt("123456")); user.setUsername(username); List<String> authList = new ArrayList<>(); authList.add("start/test"); securityUser.setAuthList(authList); } return securityUser; } }
7、maven依赖
<jwt.version>0.7.0</jwt.version> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>${jwt.version}</version> </dependency>
8、resources
application
server: port: 8090 servlet: context-path: /security spring: thymeleaf: prefix: classpath:/templates/ suffix: .html mybatis: type-aliases-package: com.jr.pojo mapper-locations: classpath:mapper/*.xml
9、SpringBoot129Application
package com.jr; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringBoot129Application { public static void main(String[] args) { SpringApplication.run(SpringBoot129Application.class,args); } }