
依赖引入功能清单及代码详解
整体配置自定义部分
1. 自定义认证2. 自定义鉴权3. 自定义异常处理4. 免鉴权资源忽略5. 登出状态清理 附
JWT token处理代码参考
实现代码以【spring-security基础】基于数据库的认证方式 为前提
jwt需要引入对应工具包,用于维护token
功能清单及代码详解io.jsonwebtoken jjwt0.9.1
- 自定义认证
- 自定义鉴权
- 自定义异常处理
- 免鉴权资源忽略
- 登出状态清理
这部分配置了自定义异常处理及过滤器
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 未授权访问
.exceptionHandling()
// 认证失败
.authenticationEntryPoint(myUnAuthEntryPoint)
// 鉴权失败
.defaultAccessDeniedHandlerFor(new MyAccessDeniedHandler(),new AntPathRequestMatcher("
@Override
public UserDetails loadUserByUsername(String usename) throws UsernameNotFoundException {
// 查询数据库
QueryWrapper queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username",usename);
UserInfo userInfo = userInfoDao.selectOne(queryWrapper);
if (userInfo ==null) {
throw new UsernameNotFoundException("用户不存在");
}
// 获取用户角色
List roles = rolePermissionDao.getRuleByUserId(userInfo.getId());
// 获取用户权限(url)
List per = rolePermissionDao.getPermissByUserId(userInfo.getId());
for (String role : roles) {
per.add("ROLE_"+role);
}
List auths = new ArrayList<>();
for (String s : per) {
GrantedAuthority e = new SimpleGrantedAuthority(s);
auths.add(e);
}
// 返回认证主体UserDetails
return new MyUser(userInfo.getUsername(),userInfo.getPassword(),auths);
//使用非自定义的密码,可能需要进行加密后再放置进认证主体密码属性中去
// return new User(userInfo.getUsername(),bCryptPasswordEncoder.encode(userInfo.getPassword()),auths);
}
}
自定义密码处理器用于密码加密(如存储到数据库前的加密)及匹配鉴权
package cn.com.demo.config;
import cn.com.demo.util.MD5;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@Component
public class MyPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
return MD5.encrypt(rawPassword.toString());
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equalsIgnoreCase(MD5.encrypt(rawPassword.toString()));
}
}
2. 自定义鉴权
自定义鉴权主要收集用户的权限,提交security进行鉴权。
jwt实现时,通过访问request的header部分查询token,并进行有效期检查及获取其权限,这部分自定义可以实现jwt token有效期的检查,同时也可以完成手动退出时token仍在有效期可继续使用的问题。
package cn.com.demo.filter;
import cn.com.demo.config.MyTokenConfig;
import cn.com.demo.util.LocalCache;
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.core.GrantedAuthority;
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.List;
public class MyTokenAuthFilter extends BasicAuthenticationFilter {
MyTokenConfig myTokenConfig;
public MyTokenAuthFilter(AuthenticationManager authenticationManager, MyTokenConfig myTokenConfig) {
super(authenticationManager);
this.myTokenConfig = myTokenConfig;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
// 获取认证成功的用户授权信息
String token = request.getHeader(myTokenConfig.getTokenConfigProperties().getHeadName());
if(!StringUtils.isEmpty(token)) {
String userName = myTokenConfig.getUserName(token);
List authorities = (List) LocalCache.get(userName);
if(authorities != null) {
// 放置权限到security上下文中
UsernamePasswordAuthenticationToken auths = new UsernamePasswordAuthenticationToken(userName, token, authorities);
SecurityContextHolder.getContext().setAuthentication(auths);
}
}
// 过滤链需要继续向下
chain.doFilter(request,response);
}
@Override
protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) throws IOException {
System.out.println("鉴权通过。。。。");
}
@Override
protected void onUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException {
System.out.println("鉴权失败");
}
}
需要注意的是,虽然覆写了onSuccessfulAuthentication、onUnsuccessfulAuthentication但并没有在执行链(doFilterdoFilterInternal)中完整处理,如需处理可参考其父类进行处理(因为这两个方法为BasicAuthenticationFilter 增强的两个方法,而非标准处理链上的方法)。
3. 自定义异常处理这部分处理包括两部分;认证过程中异常的处理及权限不足时的处理,处理这部分主要用于在用户没有权限时覆盖默认的page转发及HTTP状态码
- 通过认证入口点(AuthenticationEntryPoint)的实现,处理认证过程中的异常处理
public class MyUnAuthEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
ResponseUtil.out(response, baseRepVo.UNAUTH);
}
}
- 通过访问限制(AccessDeniedHandler)接口的实现,可根据url配置不通的处理器
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
ResponseUtil.out(response, baseRepVo.FORBIDDEN);
}
}
4. 免鉴权资源忽略
通过重写configure(WebSecurity webSecurity) 方法,配置其ignoring().antMatchers(String… antPatterns)方法 配置忽略过滤的资源,需要注意的是这里配置的资源将不会完整执行security的过滤器链,比如如果将认证过滤器(自定义)的url配置到这里,你的自定义认证过滤器也就不会生效了。
具体代码见整体配置。
通过实现LogoutHandler 接口完成退出登录的清理工作,需要注意的是,security有自己的会话维护(实现了SecurityContextHolderStrategy接口)。
public class MyLogoutHandler implements LogoutHandler {
@Autowired
private MyTokenConfig myTokenConfig;
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
String token = request.getHeader(myTokenConfig.getTokenConfigProperties().getHeadName());
if(!StringUtils.isEmpty(token)) {
// token清理工作,如在redis中删除token,防止未过期的token继续使用
String userName = myTokenConfig.getUserName(token);
LocalCache.remove(userName);
}
ResponseUtil.ok(response);
}
}
附
JWT token处理
token的处理主要包括基于用户名(用户信息)生成token,及对token的解析(这里仅在token中放置了用户名信息)
package cn.com.demo.config;
import io.jsonwebtoken.*;
import io.jsonwebtoken.impl.compression.GzipCompressionCodec;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class MyTokenConfig {
@Autowired
TokenConfigProperties tokenConfigProperties;
public String getToken(String userName) {
return Jwts.builder()
.setSubject(userName)
.setExpiration(new Date(System.currentTimeMillis() + tokenConfigProperties.getExpireTime()*1000))
.signWith(SignatureAlgorithm.HS512,tokenConfigProperties.getSignKey())
.compressWith(new GzipCompressionCodec()).compact();
}
public String getUserName(String token) {
try {
return Jwts.parser().setSigningKey(tokenConfigProperties.getSignKey()).parseClaimsJws(token).getBody().getSubject();
} catch (Exception e) {
e.printStackTrace();
System.out.println("token 解析异常");
}
return null;
}
public TokenConfigProperties getTokenConfigProperties(){
return tokenConfigProperties;
}
}
代码参考
demo代码地址 demo
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)