SpringSecurity+JWT
我丢到了一个包下没有细分,请勿模仿
数据库表
application.yml
jwt:
secret: secret
expiration: 7200000
token: Authorization
SecurityUserDetails
这里继承了我自己的实体类
package com.experiment.blog.security;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.experiment.blog.mapper.UserBlogMapper;
import com.experiment.blog.pojo.DTO.UserUpload;
import com.experiment.blog.pojo.UserBlog;
import com.experiment.blog.service.UserBlogService;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class SecurityUserDetails extends UserBlog implements UserDetails {
private Collection<? extends GrantedAuthority> authorities;
public SecurityUserDetails(String userName, String password, Collection<? extends GrantedAuthority> authorities){ this.authorities = authorities;
this.setUsername(userName);
this.setPassword(password);
this.setAuthorities(authorities);
}
/**
* 账户是否过期
* @return
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 是否禁用
* @return
*/
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* 密码是否过期
* @return
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 是否启用
* @return
*/
@Override
public boolean isEnabled() {
return true;
}
}
UserDetailsService
这里RoleBlog实体类要实现GrantedAuthority接口,实现它的getAuthority方法,返回权限名roleName
package com.experiment.blog.security;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.experiment.blog.exception.MyDefineException;
import com.experiment.blog.pojo.RoleBlog;
import com.experiment.blog.pojo.UserBlog;
import com.experiment.blog.service.*;
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.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class JwtUserDetailsService implements UserDetailsService {
@Autowired
UserBlogService userBlogService;
@Autowired
RoleBlogService roleBlogService;
@Autowired
RoleUserBlogService roleUserBlogService;
@Override
/**
* 这里通过UserName从数据库里取出权限和密码
*/
public UserDetails loadUserByUsername (String userName) throws UsernameNotFoundException {
System.out.println("JwtUserDetailsService:" + userName);
Long userId = userBlogService.getIdByUserName(userName);
if (null==userId){
throw new UsernameNotFoundException("用户名不存在");
}
List<Long> roleIds = roleUserBlogService.getRoleIdByUserId(userId);
String password = userBlogService.getOne(new QueryWrapper<UserBlog>().eq("username",userName)).getPassword();
return new SecurityUserDetails(userName,password,roleBlogService.list(new QueryWrapper<RoleBlog>().in("role_id",roleIds)));
}
}
JWT模块
JwtTokenUtil
package com.experiment.blog.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Clock;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.DefaultClock;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtTokenUtil implements Serializable {
private static final long serialVersionUID = -3301605591108950415L;
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
@Value("${jwt.token}")
private String tokenHeader;
private Clock clock = DefaultClock.INSTANCE;
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put(tokenHeader,userDetails.getUsername());
claims.put("created",new Date());
return doGenerateToken(claims, userDetails.getUsername());
}
private String doGenerateToken(Map<String, Object> claims, String subject) {
final Date createdDate = clock.now();
final Date expirationDate = calculateExpirationDate(createdDate);
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(createdDate)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
private Date calculateExpirationDate(Date createdDate) {
return new Date(createdDate.getTime() + expiration);
}
public Boolean validateToken(String token, UserDetails userDetails) {
SecurityUserDetails user = (SecurityUserDetails) userDetails;
final String username = getUsernameFromToken(token);
return (username.equals(user.getUsername())
&& !isTokenExpired(token)
);
}
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(clock.now());
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
}
JwtAuthorizationTokenFilter
package com.experiment.blog.security;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
private final UserDetailsService userDetailsService;
private final JwtTokenUtil jwtTokenUtil;
private final String tokenHeader;
public JwtAuthorizationTokenFilter(@Qualifier("jwtUserDetailsService") UserDetailsService userDetailsService,
JwtTokenUtil jwtTokenUtil, @Value("${jwt.token}") String tokenHeader) {
this.userDetailsService = userDetailsService;
this.jwtTokenUtil = jwtTokenUtil;
this.tokenHeader = tokenHeader;
}
@Override
protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
final String requestHeader = request.getHeader(this.tokenHeader);
String username = null;
String authToken = null;
if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
authToken = requestHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(authToken);
} catch (ExpiredJwtException e) {
}
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
}
JwtAuthenticationEntryPoint
没有凭证时的处理
package com.experiment.blog.security;
import com.alibaba.fastjson.JSON;
import com.experiment.blog.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;
import java.io.PrintWriter;
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
//没有凭证时走这里
@Override
public void commence (HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
System.out.println("JwtAuthenticationEntryPoint:"+e.getMessage());
returnFailure(httpServletResponse);
}
public void returnFailure(HttpServletResponse response) throws IOException{
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
CommonResult commonResult = new CommonResult<>(403,"没有凭证");
writer.write(JSON.toJSONString(commonResult));
writer.flush();
}
}
获取传入的用户名密码
UsernamePasswordFilter
package com.experiment.blog.security;
import com.experiment.blog.exception.MyDefineException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
public class UsernamePasswordFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if(MediaType.APPLICATION_JSON_VALUE.equals(request.getContentType())){
String username=null;
String password=null;
try{
// Map<String,String > map = new ObjectMapper().readValue(request.getInputStream(), Map.class);
username =request.getParameter("username");
password=request.getParameter("password");
}catch (Exception e){
throw new MyDefineException(405,"传入账号密码出错");
}
if(username==null){ throw new MyDefineException(405,"用户名为空");};
if(password==null){throw new MyDefineException(405,"密码为空");}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
return super.attemptAuthentication(request, response);
}
}
验证访问权限
MyInvocationSecurityMetadataSourceService
package com.experiment.blog.security;
import com.experiment.blog.pojo.DTO.RolePermission;
import com.experiment.blog.service.PermissionBlogService;
import com.experiment.blog.service.RolePermissionBlogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
@Component
public class MyInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource {
@Autowired
PermissionBlogService permissionBlogService;
@Autowired
RolePermissionBlogService rolePermissionBlogService;
private static HashMap<String,Collection<ConfigAttribute>> map = null;
@Override
public Collection<ConfigAttribute> getAttributes (Object o) throws IllegalArgumentException {
if (null == map){
loadResourceDefine();
}
HttpServletRequest request = ((FilterInvocation)o).getHttpRequest();
for (Iterator<String> it = map.keySet().iterator();it.hasNext();){
String url = it.next();
if (new AntPathRequestMatcher(url).matches(request)){
return map.get(url);
}
}
return null;
}
/**
* 初始化资源
*/
public void loadResourceDefine(){
map=new HashMap<>(16);
List<RolePermission> rolePermissions = rolePermissionBlogService.getRolePermission();
rolePermissions.forEach(rolePermission -> {
String url = rolePermission.getPermissionUrl();
ConfigAttribute role = new SecurityConfig(rolePermission.getRoleName());
if (map.containsKey(url)){
map.get(url).add(role);
}
else {
List<ConfigAttribute> list = new ArrayList<>();
list.add(role);
map.put(url,list);
}
});
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes () {
return null;
}
@Override
public boolean supports (Class<?> aClass) {
return true;
}
}
MyAccessDecisionManager
package com.experiment.blog.security;
import org.mybatis.logging.Logger;
import org.mybatis.logging.LoggerFactory;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.Iterator;
@Component
public class MyAccessDecisionManager implements AccessDecisionManager {
private final static Logger logger = LoggerFactory.getLogger(MyAccessDecisionManager.class);
/**
* 通过传递的参数来决定用户是否有访问对应受保护对象的权限
*
* @param authentication 包含了当前的用户信息,包括拥有的权限。这里的权限来源就是前面登录时UserDetailsService中设置的authorities。
* @param o 就是FilterInvocation对象,可以得到request等web资源
* @param collection configAttributes是本次访问需要的权限
*/
@Override
public void decide (Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
if(null == collection || 0>=collection.size()){
return;
}
else {
String needRole;
for(Iterator<ConfigAttribute> iter = collection.iterator(); iter.hasNext(); ) {
needRole = iter.next().getAttribute();
for(GrantedAuthority ga : authentication.getAuthorities()) {
if(needRole.trim().equals(ga.getAuthority().trim())) {
return;
}
}
}
throw new AccessDeniedException("当前访问没有权限");
}
}
@Override
public boolean supports (ConfigAttribute configAttribute) {
return true;
}
@Override
public boolean supports (Class<?> aClass) {
return true;
}
}
MyFilterSecurityInterceptor
package com.experiment.blog.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import java.io.IOException;
@Component
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
@Autowired
private FilterInvocationSecurityMetadataSource securityMetadataSource;
@Autowired
public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager)
{
super.setAccessDecisionManager(myAccessDecisionManager);
}
@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
invoke(fi);
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
//执行下一个拦截器
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
}
登陆成功返回token
LoginSuccessHandler
package com.experiment.blog.security;
import com.alibaba.fastjson.JSON;
import com.experiment.blog.common.CommonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Autowired
UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
public void onAuthenticationSuccess (HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
final UserDetails userDetails = userDetailsService.loadUserByUsername(authentication.getName());
final String token = jwtTokenUtil.generateToken(userDetails);
returnToken(httpServletResponse,token);
}
public void returnToken(HttpServletResponse response,String token) throws IOException {
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
Map<String,String> map=new HashMap<>();
map.put("token",token);
CommonResult<Map<String, String>> mapCommonResult = new CommonResult<>(200,"登入成功",map);
writer.write(JSON.toJSONString(mapCommonResult));
writer.flush();
}
}
登陆失败
LoginFailureHandler
package com.experiment.blog.security;
import com.alibaba.fastjson.JSON;
import com.experiment.blog.common.CommonResult;
import com.experiment.blog.exception.MyDefineException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure (HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
returnFailure(httpServletResponse);
}
public void returnFailure(HttpServletResponse response) throws IOException{
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
CommonResult commonResult = new CommonResult<>(401,"用户名或密码错误");
writer.write(JSON.toJSONString(commonResult));
writer.flush();
}
}
SecurityConfig
package com.experiment.blog.security;
import org.springframework.beans.factory.annotation.Autowired;
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.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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.DigestUtils;
@Configuration
@EnableWebSecurity //开启Security
@EnableGlobalMethodSecurity(prePostEnabled = true)//判断用户对某个控制层的方法是否具有访问权限
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
JwtUserDetailsService jwtUserDetailsService;
@Autowired
JwtAuthorizationTokenFilter jwtAuthorizationTokenFilter;
@Autowired
LoginSuccessHandler loginSuccessHandler;
@Autowired
LoginFailureHandler loginFailureHandler;
//先来这里认证一下
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService( jwtUserDetailsService ).passwordEncoder( new PasswordEncoder() {
//对密码进行加密
@Override
public String encode(CharSequence charSequence) {
System.out.println("/*********charSequence.toString()"+charSequence.toString());
return new BCryptPasswordEncoder().encode(charSequence);
}
//对密码进行判断匹配
@Override
public boolean matches(CharSequence charSequence, String s) {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
return bCryptPasswordEncoder.matches(charSequence,s);
}
} );
}
//拦截在这配
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/login**","/blog/user-blog/insert").permitAll()
.anyRequest().authenticated();
http.addFilterAt(usernamePasswordFilter(),UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(jwtAuthorizationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
//加密
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
public void configure(WebSecurity web){
web.ignoring().antMatchers("/js/**","/css/**","/fail_url","/static/**","/img/**","/img/carousel/**","/swagger-ui.html","/webjars/**","/v2/**","/swagger-resources/**");
}
@Bean
public UsernamePasswordFilter usernamePasswordFilter() throws Exception {
UsernamePasswordFilter filter=new UsernamePasswordFilter();
//重用WebSecurityConfigurerAdapter配置的AuthenticationManager
filter.setAuthenticationManager(super.authenticationManager());
filter.setFilterProcessesUrl("/login");
filter.setAuthenticationSuccessHandler(loginSuccessHandler);
filter.setAuthenticationFailureHandler(loginFailureHandler);
return filter;
}
参考
- https://www.cnblogs.com/pjjlt/p/10960690.html
- https://www.cnblogs.com/foshuo-cv/archive/2020/05/07/12842075.html