springsecurity是一个权限管理框架,由多个过滤器链组成,本次主要使用springsecurity结合jwt生成token进行前后端分离的权限校验。
权限这块使用了传统的RBAC权限控制,由五张表组成。
引入依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
准备工具类:
登录需要的对象:
@Data
public class UserLoginDto {
private String username;
private String password;
}
token存储的信息:
@Data
public class UserInfoDto {
private String username;
private List<String> perms;
}
jwt工具类:
public class JwtTokenUtils {
public static final String TOKEN_HEADER = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
public static final String SECRET = "HQ";
public static final String ISS = "echisan";
// 过期时间3小时
private static final Long EXPIRATION = 60 * 60 * 3L;
private static final String ROLE = "role";
/**
* 创建token
* @param dto 待生成token的对象
* @return java.lang.String token字符串
* @author HQ
* @date 2021/3/23 16:02
*/
public static String createToken(UserInfoDto dto) {
Map map = new HashMap();
// 将对象的权限转换成json
map.put(ROLE, JacksonUtil.toJson(dto.getPerms()));
return Jwts.builder()
.signWith(SignatureAlgorithm.HS512, SECRET)
.setClaims(map)
.setIssuer(ISS)
.setSubject(dto.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION * 1000))
.compact();
}
/**
* 获取token中的用户名
*
* @param token token
* @return java.lang.String 用户名
* @author HQ
* @date 2021/3/23 16:02
*/
public static String getUserName(String token) {
String username;
try {
username = getTokenBody(token).getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
/**
* 获取token中的权限
*
* @param token token
* @return java.lang.String 权限集合的字符串
* @author HQ
* @date 2021/3/23 16:01
*/
public static String getUserRole(String token) {
return (String) getTokenBody(token).get(ROLE);
}
private static Claims getTokenBody(String token) {
Claims claims = null;
try {
claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
} catch (ExpiredJwtException e) {
e.printStackTrace();
} catch (UnsupportedJwtException e) {
e.printStackTrace();
} catch (MalformedJwtException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
return claims;
}
/**
* 判断token是否过期
*
* @param token 待验证token
* @return boolean true未过期,false过期
* @author HQ
* @date 2021/3/23 16:00
*/
public static boolean isExpiration(String token) {
try {
return getTokenBody(token).getExpiration().before(new Date());
} catch (Exception e) {
System.out.println(e.getMessage());
}
return true;
}
}
开始集成security
实现UserDetailsService接口,重写loadUserByUsername方法。该方法是security的核心方法,返回UserDetails对象。
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 根据用户名查询用户信息
SysUser sysUser = sysUserService.getOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username));
// 如果未查询到用户信息抛出异常 用户名或密码错误
if (sysUser == null) {
throw new UserCustomException(ResponeEnum.USERNAME_OR_PASSWORD_ERROR);
}
// 用户存在 查询用户的权限
// 判断用户是不是超级管理员
List<String> perms;
if ("admin".equals(username)) {
List<SysMenu> sysMenuList = sysMenuService.findAllCode();
perms = (sysMenuList.stream().map(SysMenu::getCode).collect(Collectors.toList()));
} else {
perms = sysUserService.findPermsByUserName(username);
}
// 查询出来的权限是字符串集合 需要构造成GrantedAuthority的集合
// 构造权限集合
List<GrantedAuthority> roles = new ArrayList<GrantedAuthority>();
for (String perm : perms) {
roles.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return perm;
}
});
}
// 返回UserDetails对象
return new User(sysUser.getUsername(), sysUser.getPassword(), roles);
}
BasicAuthenticationFilter重写身份认证过滤器。
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
/**
* 过滤请求
*
* @param request
* @param response
* @param chain
* @author HQ
* @date 2021/3/23 16:14
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
// 获取请求头的token信息
String tokenHeader = request.getHeader(JwtTokenUtils.TOKEN_HEADER);
// 如果请求头中没有Authorization信息则直接放行
if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtils.TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
// 如果请求头中有token,则进行解析,并且设置认证信息
// 验证token是否过期
if (!JwtTokenUtils.isExpiration(tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, ""))) {
UsernamePasswordAuthenticationToken authentication = getAuthentication(tokenHeader);
// 通过上下文设置认证信息
SecurityContextHolder.getContext().setAuthentication(authentication);
super.doFilterInternal(request, response, chain);
} else {
chain.doFilter(request, response);
return;
}
}
/**
* 根据token获取认证信息
* @param tokenHeader 待验证token
* @return org.springframework.security.authentication.UsernamePasswordAuthenticationToken 认证信息
* @author HQ
* @date 2021/3/23 16:16
*/
private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) {
// 替换前缀
String token = tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, "");
// 获取token中的username
String username = JwtTokenUtils.getUserName(token);
// 如果username不存在 直接抛出异常
if (StringUtils.isBlank(username)){
throw new UserCustomException(ResponeEnum.TOKEN_ERROR);
}
// 获得权限 添加到权限上去
String role = JwtTokenUtils.getUserRole(token);
if (StringUtils.isBlank(role)){
// 如果role不存在 直接抛出异常
throw new UserCustomException(ResponeEnum.TOKEN_ERROR);
}
// 将字符串转成list
List<String> list = JacksonUtil.toObject(role, List.class);
// 构造认证信息需要的GrantedAuthority对象集合
List<GrantedAuthority> roles = new ArrayList<GrantedAuthority>();
for (String aus : list) {
roles.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return aus;
}
});
}
// 返回认证信息 包含用户名权限
return new UsernamePasswordAuthenticationToken(username, null, roles);
}
}
AuthenticationEntryPoint实现认证失败的接口 当认证失败的时候返回
@Slf4j
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
log.info("用户没有携带正确的token:{}",e.getMessage());
SecurityResUtil.write(httpServletResponse, R.fail(ResponeEnum.TOKEN_ERROR));
}
}
public class SecurityResUtil {
public static void write(HttpServletResponse response, R r) throws IOException {
response.setContentType("application/json;charset=utf-8");
response.setStatus(200);
PrintWriter writer = response.getWriter();
String res = JacksonUtil.toJson(r);
writer.write(res);
writer.flush();
writer.close();
}
}
登录实现
/**
* 登录
* @param dto 登录信息
* @return java.lang.String token字符串
* @author HQ
* @date 2021/3/23 16:28
*/
@Override
public String login(UserLoginDto dto) {
// 登陆验证
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(dto.getUsername(), dto.getPassword());
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
// 验证通过
// 查询用户的权限
// 判断用户是不是超级管理员
List<String> perms;
if ("admin".equals(dto.getUsername())) {
List<SysMenu> sysMenuList = sysMenuService.findAllCode();
perms = (sysMenuList.stream().map(SysMenu::getCode).collect(Collectors.toList()));
} else {
perms = sysUserService.findPermsByUserName(dto.getUsername());
}
// 构造返回的数据
UserInfoDto infoDto = new UserInfoDto();
infoDto.setPerms(perms);
infoDto.setUsername(dto.getUsername());
// 返回token字符串
return JwtTokenUtils.createToken(infoDto);
}
@RestController
public class AuthController {
@Autowired
private AuthSerivce authSerivce;
@PostMapping("/login")
public R login(@RequestBody UserLoginDto dto){
String token = authSerivce.login(dto);
return R.success(token);
}
}
配置security的配置类WebSecurityConfigurerAdapter
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityJwtConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**")
.permitAll()
.antMatchers("/").permitAll()
//login 不拦截
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
//授权
.and()
// 禁用session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 使用自己定义的拦截机制,拦截jwt
http.addFilterBefore(new JwtAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class)
// 授权错误信息处理
.exceptionHandling()
// 用户访问资源没有携带正确的token
.authenticationEntryPoint(jwtAuthenticationEntryPoint);
}
@Bean
public PasswordEncoder passwordEncoder() {
// 密码加密
return new BCryptPasswordEncoder();
}
}
配置完成,登录返回的token在请求是需要加到请求头:
Authorization Bearer +token
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)