在spring_security引入相关依赖
<dependencies>
<!-- Spring Security依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
</dependencies>
创建spring security核心配置类
Spring Security的核心配置就是继承WebSecurityConfigurerAdapter并注解@EnableWebSecurity的配置。
这个配置指明了用户名密码的处理方式、请求路径的开合、登录登出控制等和安全相关的配置
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
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.core.userdetails.UserDetailsService;
/**
* <p>
* Security配置类
* </p>
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
private UserDetailsService userDetailsService;
private TokenManager tokenManager;
private DefaultPasswordEncoder defaultPasswordEncoder;
private RedisTemplate redisTemplate;
@Autowired
public TokenWebSecurityConfig(UserDetailsService userDetailsService, DefaultPasswordEncoder defaultPasswordEncoder,
TokenManager tokenManager, RedisTemplate redisTemplate) {
this.userDetailsService = userDetailsService;
this.defaultPasswordEncoder = defaultPasswordEncoder;
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}
/**
* 配置设置
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(new UnauthorizedEntryPoint())
.and().csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and().logout().logoutUrl("/admin/acl/index/logout")
.addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate)).and()
.addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate))
.addFilter(new TokenAuthenticationFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();
}
/**
* 密码处理
* @param auth
* @throws Exception
*/
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);
}
/**
* 配置哪些请求不拦截
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/**",
"/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**"
);
}
}
创建认证授权相关的工具类
DefaultPasswordEncoder:密码处理的方法
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
/**
* <p>
* 密码的处理方法类型
* </p>
*/
@Component
public class DefaultPasswordEncoder implements PasswordEncoder {
public DefaultPasswordEncoder() {
this(-1);
}
/**
* @param strength
* the log rounds to use, between 4 and 31
*/
public DefaultPasswordEncoder(int strength) {
}
public String encode(CharSequence rawPassword) {
return MD5.encrypt(rawPassword.toString());
}
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));
}
}
TokenManager:token操作的工具类
import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* <p>
* token管理
* </p>
*/
@Component
public class TokenManager {
private long tokenExpiration = 24*60*60*1000;
private String tokenSignKey = "123456";
public String createToken(String username) {
String token = Jwts.builder().setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
.signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();
return token;
}
public String getUserFromToken(String token) {
String user = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();
return user;
}
public void removeToken(String token) {
//jwttoken无需删除,客户端扔掉即可。
}
}
TokenLogoutHandler:退出实现
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* <p>
* 登出业务逻辑类
* </p>
*/
public class TokenLogoutHandler implements LogoutHandler {
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
public TokenLogoutHandler(TokenManager tokenManager, RedisTemplate redisTemplate) {
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
String token = request.getHeader("token");
if (token != null) {
tokenManager.removeToken(token);
//清空当前用户缓存中的权限数据
String userName = tokenManager.getUserFromToken(token);
redisTemplate.delete(userName);
}
ResponseUtil.out(response, R.ok());
}
}
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;
/**
* <p>
* 未授权的统一处理方式
* </p>
*/
public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
ResponseUtil.out(response, R.error());
}
}
创建认证授权实体类
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* <p>
* 安全认证用户详情信息
* </p>
*/
@Data
@Slf4j
public class SecurityUser implements UserDetails {
//当前登录用户
private transient User currentUserInfo;
//当前权限
private List<String> permissionValueList;
public SecurityUser() {
}
public SecurityUser(User user) {
if (user != null) {
this.currentUserInfo = user;
}
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
for(String permissionValue : permissionValueList) {
if(StringUtils.isEmpty(permissionValue)) continue;
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
authorities.add(authority);
}
return authorities;
}
@Override
public String getPassword() {
return currentUserInfo.getPassword();
}
@Override
public String getUsername() {
return currentUserInfo.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* <p>
* 用户实体类
* </p>
*/
@Data
@ApiModel(description = "用户实体类")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "微信openid")
private String username;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "昵称")
private String nickName;
@ApiModelProperty(value = "用户头像")
private String salt;
@ApiModelProperty(value = "用户签名")
private String token;
}
创建认证和授权的filter
TokenLoginFilter:认证的filter
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.data.redis.core.RedisTemplate;
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;
/**
* <p>
* 登录过滤器,继承UsernamePasswordAuthenticationFilter,对用户名密码进行登录校验
* </p>
*/
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) {
this.authenticationManager = authenticationManager;
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
this.setPostOnly(false);
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException {
try {
User user = new ObjectMapper().readValue(req.getInputStream(), User.class);
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 登录成功
* @param req
* @param res
* @param chain
* @param auth
* @throws IOException
* @throws ServletException
*/
@Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
Authentication auth) throws IOException, ServletException {
SecurityUser user = (SecurityUser) auth.getPrincipal();
String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());
redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(), user.getPermissionValueList());
ResponseUtil.out(res, R.ok().data("token", token));
}
/**
* 登录失败
* @param request
* @param response
* @param e
* @throws IOException
* @throws ServletException
*/
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException e) throws IOException, ServletException {
ResponseUtil.out(response, R.error());
}
}
TokenAuthenticationFilter:
授权filter
import org.springframework.data.redis.core.RedisTemplate;
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 org.springframework.util.StringUtils;
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 java.util.Collection;
import java.util.List;
/**
* <p>
* 访问过滤器
* </p>
*/
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
public TokenAuthenticationFilter(AuthenticationManager authManager, TokenManager tokenManager,RedisTemplate redisTemplate) {
super(authManager);
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws IOException, ServletException {
logger.info("================="+req.getRequestURI());
if(req.getRequestURI().indexOf("admin") == -1) {
chain.doFilter(req, res);
return;
}
UsernamePasswordAuthenticationToken authentication = null;
try {
authentication = getAuthentication(req);
} catch (Exception e) {
ResponseUtil.out(res, R.error());
}
if (authentication != null) {
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
ResponseUtil.out(res, R.error());
}
chain.doFilter(req, res);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
// token置于header里
String token = request.getHeader("token");
if (token != null && !"".equals(token.trim())) {
String userName = tokenManager.getUserFromToken(token);
List<String> permissionValueList = (List<String>) redisTemplate.opsForValue().get(userName);
Collection<GrantedAuthority> authorities = new ArrayList<>();
for(String permissionValue : permissionValueList) {
if(StringUtils.isEmpty(permissionValue)) continue;
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
authorities.add(authority);
}
if (!StringUtils.isEmpty(userName)) {
return new UsernamePasswordAuthenticationToken(userName, token, authorities);
}
return null;
}
return null;
}
}