**
## springboot集成springSecurity,jwt实现授权,查看权限,获取用户信息;】
简单的springsecurity授权登录校验我就暂时不写了,博客太多了;
第一步:还是导入相关依赖;
io.jsonwebtoken
jjwt
org.springframework.boot
spring-boot-starter-security
接下来就是书写实现springsecurity的那个自定义 UserDetails接口,这个很重要,没有这个,后面做不了那个权限分配,账号密码校验,角色分配;
import com.alibaba.fastjson.annotation.JSONField;
import com.lihua.pojo.entity.DtsAdminEntity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {
private static final long serialVersionUID = 1905122041950251207L;
private DtsAdminEntity adminEntity;
private List roleList;
private List permission;
@JSONField(serialize = false)
private List authorities;
public LoginUser(DtsAdminEntity adminEntity, List permissionList, List roleList) {
this.adminEntity = adminEntity;
this.permission = permissionList;
this.roleList = roleList;
}
@Override
public Collection extends GrantedAuthority> getAuthorities() {
authorities = new ArrayList<>();
for (String roles : roleList) {
authorities.add(new SimpleGrantedAuthority(roles));
}
for (Integer per : permission) {
authorities.add(new SimpleGrantedAuthority(per.toString()));
}
return authorities;
}
@Override
public String getPassword() {
return new BCryptPasswordEncoder().encode(adminEntity.getPassword());
}
@Override
public String getUsername() {
return this.adminEntity.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;
}
}
注意:DtsAdminEntity 是你自定义的那个用户信息类实体,这个里面最好包含那个用户名(账户名)username,密码(pssword),以及角色id,或者账号是否过期的标识,一般返回那个正确标识的那个结果;
接下来就就是你有哪些权限,这个最好是返回那个权限菜单的主键标识,方便你后面查表,获取权限列表;
return new BCryptPasswordEncoder().encode(adminEntity.getPassword());其中这行代码相当关键,是你密码校验中的精髓,注意必须要满足那个springSecurity加密方式,不然你后面每次改了密码或者新增用户每次都会出现你的密码错误,拿不到那个令牌,导致你总是登录失败;
好了接下来就是比较重要的一个环节,注册登录,但是这里我就不弄那个注册了,密码一定要符合那个springsecurity那个加密方式,可以加盐,但是这里我就不做演示了;登录的代码:
控制层:
@CrossOrigin
@PostMapping(value = "login")
public ResponseResult login(@RequestBody MerberLoginVo merberLoginVo) {
String token = dtsAdminService.login(merberLoginVo.getUsername(), merberLoginVo.getPassword());
if (StringUtils.isEmpty(token)) {
return ResponseResult.setResult(ResponseEnum.ERROR);
}
return ResponseResult.ok().data("token", token);
}
这个service实现层代码:
首先注入
// 记住:这个是特别重要的,决定你能不能登录!
@Autowired
private AuthenticationManager authenticationManager;
// 这个是把那个人信息放到那个redis缓存中,会过期的,所以想要在线时间长点,建议把那个jwt过期时间设置长点!
@Autowired
private RedisCache redisCache;
登录实现方法:
@Override
public String login(String username, String password) {
//使用Authentication的实现类
Authentication authentication = new UsernamePasswordAuthenticationToken(username, password);
//手动调用方法去认证 他会自动调用UserDetailsService查 然后对比啥的
Authentication authenticate = authenticationManager.authenticate(authentication);
if (Objects.isNull(authenticate)) {
//说明输入错误
throw new RuntimeException("用户名或密码错误");
}
//拿到用户信息 然后生成jwt返回给前端,并且将用户的信息存入redis,这个其实就是UserDetails 也就是LoginUser
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userId = loginUser.getAdminEntity().getId().toString();
String jwt = JwtUtil.createJwt(userId);
//将用户信息直接存入redis
redisCache.setCacheObject("login:" + userId, loginUser);
return jwt;
}
jwt工具类:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
/**
* JWT工具类
*
* @author THUNOEROBOT
*/
public class JwtUtil {
/**
* 有效期为 60 * 60 *1000 一个小时
*/
public static final Long JWT_TTL = 24 * 60 * 60 * 1000L;
/**
* 设置秘钥明文
*/
public static final String JWT_KEY = "yunduo";
public static String getUuid() {
return UUID.randomUUID().toString().replace("-", "");
}
/**
* 生成jtw
* 设置过期时间
*
* @param subject token中要存放的数据(json格式)
* @return jwt字符串
*/
public static String createJwt(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUuid());
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if (ttlMillis == null) {
ttlMillis = JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid)
.setSubject(subject)
.setIssuer("yunduo")
.setIssuedAt(now)
.signWith(signatureAlgorithm, secretKey)
.setExpiration(expDate);
}
public static void main(String[] args) {
String token = "输入你的幸运字符";
Claims claims = parseJwt(token);
}
/**
* 生成加密后的秘钥 secretKey
*
* @return secretKey
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
return new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
}
/**
* 解析
*
* @param jwt jwt
* @return 对象用户
*/
public static Claims parseJwt(String jwt) {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}
redis缓存类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* @author THUNOEROBOT
*/
@Component
public class RedisCache
{
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public void setCacheObject(final String key, final T value)
{
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
{
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout)
{
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit)
{
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public T getCacheObject(final String key)
{
ValueOperations operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key)
{
return redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public long deleteObject(final Collection collection)
{
return redisTemplate.delete(collection);
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public long setCacheList(final String key, final List dataList)
{
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public List getCacheList(final String key)
{
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public BoundSetOperations setCacheSet(final String key, final Set dataSet)
{
BoundSetOperations setOperation = redisTemplate.boundSetOps(key);
for (T t : dataSet) {
setOperation.add(t);
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public Set getCacheSet(final String key)
{
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public void setCacheMap(final String key, final Map dataMap)
{
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public Map getCacheMap(final String key)
{
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public void setCacheMapValue(final String key, final String hKey, final T value)
{
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public T getCacheMapValue(final String key, final String hKey)
{
HashOperations opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 删除Hash中的数据
*
* @param key
* @param hkey
*/
public void delCacheMapValue(final String key, final String hkey)
{
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.delete(key, hkey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public List getMultiCacheMapValue(final String key, final Collection
redis序列化配置:
import com.lihua.utils.FastJsonRedisSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @author THUNOEROBOT
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate
测试一下,验证一下:
拿到了我们的token,接下来就是书写那个配置,这个是整个项目的核心,缺少这个,项目是不完美的,而且你的项目有可能是启动失败的,会突然跑不起来!!!
接下来讲下如何配置
import com.lihua.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
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.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private AccessDeniedHandler accessDeniedHandler;
token过滤配置注入,注意这个时候的
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问
.antMatchers("/**/login").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers("/css/**",
"/js/**",
"/index.html",
"/img/**",
"/fonts/**",
"/favicon.ico",
"/verifyCode");
}
}
接下来就是书写那个token过滤器,注意没有这个,你无法在实现上下文携带token操作,无法在前后端项目中流畅的操作
import com.lihua.config.RedisCache;
import com.lihua.jwt.JwtUtil;
import com.lihua.jwt.LoginUser;
import io.jsonwebtoken.Claims;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
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;
import java.util.Objects;
/**
* @author
*/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisCache redisCache;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//我们先拿到请求头中的token
String token = request.getHeader("token");
if(StringUtils.isBlank(token)){
//说明没有携带token 那么直接放行 之后的过滤器肯定会报错,那么就说明用户没有登录
filterChain.doFilter(request,response);
return;
}
//解析token
String userid;
try {
Claims claims = JwtUtil.parseJwt(token);
userid = claims.getSubject();
} catch (Exception e) {
e.printStackTrace();
//就说明token失效了 或者是token无效
throw new RuntimeException("token无效");
}
//从redis中拿到用户的信息,给SecurityContextHolder设置上下文
LoginUser loginUser = redisCache.getCacheObject("login:" + userid);
if(Objects.isNull(loginUser)){
throw new RuntimeException("用户未登录");
}
//存入SecurityContextHolder上下文当中 注意 这里必须得使用三个参数的authentication
//第三个参数则为权限
Authentication authentication = new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
//放行
filterChain.doFilter(request,response);
}
}
接下来就是自定义配置那个未授权的配置了,不可能使用那个原生的springsecurity的未授权页面,那样就得你很不专业,就像我昨天说这个框架---》春季安全框架,是没错,但是你好不专业!
import com.alibaba.fastjson.JSON;
import com.lihua.result.ResponseEnum;
import com.lihua.result.ResponseResult;
import com.lihua.utils.WebUtils;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) {
response.setStatus(HttpStatus.FORBIDDEN.value());
ResponseResult result = ResponseResult.setResult(ResponseEnum.NO_PERMISSIONS);
WebUtils.renderString(response, JSON.toJSONString(result));
}
}
import com.alibaba.fastjson.JSON;
import com.lihua.result.ResponseEnum;
import com.lihua.result.ResponseResult;
import com.lihua.utils.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author THUNOEROBOT
*/
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
//401 表示没有授权
response.setStatus(HttpStatus.UNAUTHORIZED.value());
ResponseResult result = ResponseResult.setResult(ResponseEnum.NO_AUTHORIZATION);
WebUtils.renderString(response, JSON.toJSONString(result));
}
}
接下来就是讲讲配置文件的作用了;我不可能写份代码给你,然后看了一脸懵逼!这是写的啥垃圾文章!!!
第一个地方,解决你前端老是报跨域啊,哎哟喂,怎么又跨域啦,头疼!!!!
整个流程就是这样了,当然如何获取个人信息,那就去看看如何解析jwt了!
好了跟大家说say good bye!嘿嘿