仿生学习系列之SpringSecurity+JWT登陆认证(二)代码篇
经过一段时间的测试博主终于算是完成了基础的SpringSecurity代码,下面就将我的部分代码展示给各位,
需要完整代码的可以加群艾特我,基本配置和包概述在上一篇文章已经讲述,这里就不再过多陈述啦,
不清楚的可以翻看前言篇。
1.---获取配置类JWTConfig
package cn.lhydemo.authority.common.config;
import lombok.Getter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Getter
@ConfigurationProperties(prefix = "jwt")
@Component
public class JWTConfig {
/**
* 密钥KEY
*/
public static String secret;
/**
* TokenKey
*/
public static String tokenHeader;
/**
* Token前缀字符
*/
public static String tokenPrefix;
/**
* 过期时间
*/
public static Integer expiration;
/**
* 不需要认证的接口
*/
public static String antMatchers;
public void setSecret(String secret) {
this.secret = secret;
}
public void setTokenHeader(String tokenHeader) {
this.tokenHeader = tokenHeader;
}
public void setTokenPrefix(String tokenPrefix) {
this.tokenPrefix = tokenPrefix;
}
public void setExpiration(Integer expiration) {
this.expiration = expiration * 1000;
}
public void setAntMatchers(String antMatchers) {
this.antMatchers = antMatchers;
}
}
2.---Security用户的实体
记住这里必须要实现UserDetails这个类
3.---JWT接口请求校验拦截器
package cn.lhydemo.authority.authority.jwt;
import cn.lhydemo.authority.authority.entity.ItemUserEntity;
import cn.lhydemo.authority.common.config.JWTConfig;
import com.alibaba.fastjson.JSONObject;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
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.List;
import java.util.Map;
/**
* JWT接口请求校验拦截器
* 请求接口时会进入这里验证Token是否合法和过期
* @author: hyli
* @create: 2020-01-08 15
*/
@Slf4j
public class JWTAuthenticationTokenFilter extends BasicAuthenticationFilter {
public JWTAuthenticationTokenFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 获取请求头中JWT的Token
String tokenHeader = request.getHeader(JWTConfig.tokenHeader);
if (null!=tokenHeader && tokenHeader.startsWith(JWTConfig.tokenPrefix+" ")) {
try {
// 截取JWT前缀
String token = tokenHeader.replace(JWTConfig.tokenPrefix+" ", "");
// 解析JWT
Claims claims = Jwts.parser()
.setSigningKey(JWTConfig.secret)
.parseClaimsJws(token)
.getBody();
// 获取用户名
String username = claims.getSubject();
String userId=claims.getId();
if(!StringUtils.isEmpty(username)&&!StringUtils.isEmpty(userId)) {
// 获取角色
List<GrantedAuthority> authorities = new ArrayList<>();
String authority = claims.get("authorities").toString();
if(!StringUtils.isEmpty(authority)){
List<Map<String,String>> authorityMap = JSONObject.parseObject(authority, List.class);
for(Map<String,String> role : authorityMap){
if(!StringUtils.isEmpty(role)) {
authorities.add(new SimpleGrantedAuthority(role.get("authority")));
}
}
}
//组装参数
ItemUserEntity itemUserEntity = new ItemUserEntity();
itemUserEntity.setUsername(claims.getSubject());
itemUserEntity.setUserId(claims.getId());
itemUserEntity.setAuthorities(authorities);
//用户名密码进行封装然后方便验证
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(itemUserEntity, userId, authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (ExpiredJwtException e){
log.info("Token过期");
} catch (Exception e) {
log.info("Token无效");
}
}
filterChain.doFilter(request, response);
return;
}
}
4.---自定义权限注解验证
package cn.lhydemo.authority.authority;
import cn.lhydemo.authority.authority.entity.ItemUserEntity;
import cn.lhydemo.authority.core.entity.SysMenuEntity;
import cn.lhydemo.authority.core.service.SysUserService;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 自定义权限注解验证
*
* @author: hyli
* @create: 2020-01-08 15
*/
@Component
public class UserPermissionEvaluator implements PermissionEvaluator {
@Resource
private SysUserService sysUserService;
/**
* hasPermission鉴权方法
* 这里仅仅判断PreAuthorize注解中的权限表达式
* 实际中可以根据业务需求设计数据库通过targetUrl和permission做更复杂鉴权
* 当然targetUrl不一定是URL可以是数据Id还可以是管理员标识等,这里根据需求自行设计
* @author: hyli
* @create: 2020-01-08 15
* @Param authentication 用户身份(在使用hasPermission表达式时Authentication参数默认会自动带上)
* @Param targetUrl 请求路径
* @Param permission 请求路径权限
* @Return boolean 是否通 过
*/
@Override
public boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) {
// 获取用户信息
ItemUserEntity itemUserEntity = (ItemUserEntity) authentication.getPrincipal();
// 查询用户权限(这里可以将权限放入缓存中提升效率)
Set<String> permissions = new HashSet<>();
List<SysMenuEntity> sysMenuEntityList = sysUserService.selectSysMenuByUserId(itemUserEntity.getUserId());
for (SysMenuEntity sysMenuEntity : sysMenuEntityList) { permissions.add(sysMenuEntity.getPermission()); }
// 权限对比
if (permissions.contains(permission.toString())) { return true; }
return false;
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { return false; }
}
5.---自定义登录验证
package cn.lhydemo.authority.authority;
import cn.lhydemo.authority.authority.entity.ItemUserEntity;
import cn.lhydemo.authority.authority.service.ItemUserDetailsService;
import cn.lhydemo.authority.core.entity.SysRoleEntity;
import cn.lhydemo.authority.core.service.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.LockedException;
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.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 自定义登录验证
* @author: hyli
* @create: 2020-01-08 15
**/
@Component
public class UserAuthenticationProvider implements AuthenticationProvider {
@Autowired
private ItemUserDetailsService itemUserDetailsService;
@Autowired
private SysUserService sysUserService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 获取表单输入中返回的用户名
String userName = (String) authentication.getPrincipal();
// 获取表单中输入的密码
String password = (String) authentication.getCredentials();
// 查询用户是否存在
ItemUserEntity userInfo = itemUserDetailsService.loadUserByUsername(userName);
if (userInfo == null) {
throw new UsernameNotFoundException("用户名不存在");
}
// 我们还要判断密码是否正确,这里我们的密码使用BCryptPasswordEncoder进行加密的
if (!new BCryptPasswordEncoder().matches(password, userInfo.getPassword())) {
throw new BadCredentialsException("密码不正确");
}
// 还可以加一些其他信息的判断,比如用户账号已停用等判断
if (userInfo.getStatus().equals("PROHIBIT")){
throw new LockedException("该用户已被冻结");
}
// 角色集合
Set<GrantedAuthority> authorities = new HashSet<>();
// 查询用户角色
List<SysRoleEntity> sysRoleEntityList = sysUserService.selectSysRoleByUserId(userInfo.getUserId());
for (SysRoleEntity sysRoleEntity: sysRoleEntityList){
authorities.add(new SimpleGrantedAuthority("ROLE_" + sysRoleEntity.getRoleName()));
}
userInfo.setAuthorities(authorities);
// 进行登录
return new UsernamePasswordAuthenticationToken(userInfo, password, authorities);
}
@Override
public boolean supports(Class<?> authentication) {
return true;
}
}
6.---一些登陆操作
上文提到的自定义权限注解@PreAuthorize用处则是在controller层用的
package cn.lhydemo.authority.controller;
import cn.lhydemo.authority.authority.entity.ItemUserEntity;
import cn.lhydemo.authority.common.util.ResultUtil;
import cn.lhydemo.authority.common.util.SecurityUtil;
import cn.lhydemo.authority.core.entity.SysMenuEntity;
import cn.lhydemo.authority.core.entity.SysRoleEntity;
import cn.lhydemo.authority.core.entity.SysUserEntity;
import cn.lhydemo.authority.core.service.SysMenuService;
import cn.lhydemo.authority.core.service.SysRoleService;
import cn.lhydemo.authority.core.service.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 管理端
* @author: hyli
* @create: 2020-01-08 16
*/
@RestController
@RequestMapping("/admin")
public class AdminController {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysRoleService sysRoleService;
@Autowired
private SysMenuService sysMenuService;
/**
* @Description: 管理端
* @return: Map<String,Object> 返回数据MAP
* @Author: Li.hy
* @Date: 2020/1/9
*/
@PreAuthorize("hasRole('ADMIN')")
@RequestMapping(value = "/info",method = RequestMethod.GET)
public Map<String,Object> userLogin(){
Map<String,Object> result = new HashMap<>();
ItemUserEntity userDetails = SecurityUtil.getUserInfo();
result.put("title","管理端信息");
result.put("data",userDetails);
return ResultUtil.resultSuccess(result);
}
/**
* @Description: 拥有ADMIN或者USER角色可以访问
* @Param:
* @return: Map<String,Object> 返回数据MAP
* @Author: Li.hy
* @Date: 2020/1/9
*/
@PreAuthorize("hasAnyRole('ADMIN','USER')")
@RequestMapping(value = "/list",method = RequestMethod.GET)
public Map<String,Object> list(){
Map<String,Object> result = new HashMap<>();
List<SysUserEntity> sysUserEntityList = sysUserService.list();
result.put("title","拥有用户或者管理员角色都可以查看");
result.put("data",sysUserEntityList);
return ResultUtil.resultSuccess(result);
}
/**
* @Description: 拥有ADMIN和USER角色可以访问
* @Param:
* @return: Map<String,Object> 返回数据MAP
* @Author: Li.hy
* @Date: 2020/1/9
*/
@PreAuthorize("hasRole('ADMIN') and hasRole('USER')")
@RequestMapping(value = "/menuList",method = RequestMethod.GET)
public Map<String,Object> menuList(){
Map<String,Object> result = new HashMap<>();
List<SysMenuEntity> sysMenuEntityList = sysMenuService.list();
result.put("title","拥有用户和管理员角色都可以查看");
result.put("data",sysMenuEntityList);
return ResultUtil.resultSuccess(result);
}
/**
* @Description: 拥有sys:user:info权限可以访问
* @Param: hasPermission 中 第一个参数是请求路径 第二个参数是权限表达式
* @return: Map<String,Object> 返回数据MAP
* @Author: Li.hy
* @Date: 2020/1/9
*/
@PreAuthorize("hasPermission('/admin/userList','sys:user:info')")
@RequestMapping(value = "/userList",method = RequestMethod.GET)
public Map<String,Object> userList(){
Map<String,Object> result = new HashMap<>();
List<SysUserEntity> sysUserEntityList = sysUserService.list();
result.put("title","拥有sys:user:info权限都可以查看");
result.put("data",sysUserEntityList);
return ResultUtil.resultSuccess(result);
}
/**
* @Description: 拥有ADMIN角色和sys:role:info权限可以访问
* @return: Map<String,Object> 返回数据MAP
* @Author: Li.hy
* @Date: 2020/1/9
*/
@PreAuthorize("hasRole('ADMIN') and hasPermission('/admin/adminRoleList','sys:role:info')")
@RequestMapping(value = "/adminRoleList",method = RequestMethod.GET)
public Map<String,Object> adminRoleList(){
Map<String,Object> result = new HashMap<>();
List<SysRoleEntity> sysRoleEntityList = sysRoleService.list();
result.put("title","拥有ADMIN角色和sys:role:info权限可以访问");
result.put("data",sysRoleEntityList);
return ResultUtil.resultSuccess(result);
}
}
> 还有一处关键项springsecurity的配置,这里你可以自定义登陆界面登出界面
package cn.lhydemo.authority.common.config;
import cn.lhydemo.authority.authority.UserAuthenticationProvider;
import cn.lhydemo.authority.authority.UserPermissionEvaluator;
import cn.lhydemo.authority.authority.handler.*;
import cn.lhydemo.authority.authority.jwt.JWTAuthenticationTokenFilter;
import org.springframework.context.annotation.Bean;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import javax.annotation.Resource;
/**
* SpringSecurity配置类
* @Author Sans
* @CreateTime 2019/10/1 9:40
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启权限注解,默认是关闭的
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 自定义登录成功处理器
*/
@Resource
private UserLoginSuccessHandler userLoginSuccessHandler;
/**
* 自定义登录失败处理器
*/
@Resource
private UserLoginFailureHandler userLoginFailureHandler;
/**
* 自定义注销成功处理器
*/
@Resource
private UserLogoutSuccessHandler userLogoutSuccessHandler;
/**
* 自定义暂无权限处理器
*/
@Resource
private UserPermissionDeniedHandler userPermissionDeniedHandler;
/**
* 自定义未登录的处理器
*/
@Resource
private UserNotAuthenticationHandler userNotAuthenticationHandler;
/**
* 自定义登录逻辑验证器
*/
@Resource
private UserAuthenticationProvider userAuthenticationProvider;
/**
* 加密方式
* @Author Sans
* @CreateTime 2019/10/1 14:00
*/
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 注入自定义PermissionEvaluator
*/
@Bean
public DefaultWebSecurityExpressionHandler userSecurityExpressionHandler(){
DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
handler.setPermissionEvaluator(new UserPermissionEvaluator());
return handler;
}
/**
* 配置登录验证逻辑
*/
@Override
protected void configure(AuthenticationManagerBuilder auth){
//这里可启用我们自己的登陆验证逻辑
auth.authenticationProvider(userAuthenticationProvider);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/static/**");
}
/**
* 配置security的控制逻辑
* @Author Sans
* @CreateTime 2019/10/1 16:56
* @Param http 请求
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 不进行权限验证的请求或资源(从配置文件中读取)
.antMatchers(JWTConfig.antMatchers.split(",")).permitAll()
// 其他的需要登陆后才能访问
.anyRequest().authenticated()
.and()
// 配置未登录自定义处理类
.httpBasic().authenticationEntryPoint(userNotAuthenticationHandler)
.and()
// 配置登录地址
.formLogin()
.loginPage("/login/index")
.loginProcessingUrl("/login/userLogin")
// 配置登录成功自定义处理类
.successHandler(userLoginSuccessHandler)
// 配置登录失败自定义处理类
.failureHandler(userLoginFailureHandler)
.and()
// 配置登出地址
.logout()
.logoutUrl("/login/userLogout")
// 配置用户登出自定义处理类
.logoutSuccessHandler(userLogoutSuccessHandler).clearAuthentication(true)
.and()
// 配置没有权限自定义处理类
.exceptionHandling().accessDeniedHandler(userPermissionDeniedHandler)
.and()
// 开启跨域
.cors()
.and()
// 取消跨站请求伪造防护
.csrf().disable();
// 基于Token不需要session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 禁用缓存
http.headers().cacheControl();
// 添加JWT过滤器
http.addFilter(new JWTAuthenticationTokenFilter(authenticationManager()));
}
}
测试-采用了freemark
自定义的登陆界面
登陆成功以后返回token
将拿到的token去postman测试(因为博主前端比较菜,就没弄那么多,直接测试的接口)
最后博主想说的是,我的编排可能会有点乱,但是肯定有些朋友只是缺某一段代码,应该能看懂,拿到完整代码也肯定是能运行的,需要的就联系博主吧,或者有文章编排的高手也请加下博主,我想请教一下,谢谢指点。后面我将更新springboot+springsecurity+oauth2整合以及将这些融入到微服务中,敬请期待吧。
正文到此结束
- 本文标签: SpringSecurity jwt
- 版权声明: 本站原创文章,于2020年01月22日由李和屹发布,转载请注明出处