1、 添加相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、自定义用户对象UserInfo
@Data
public class UserInfo {
private int id;
//姓名
private String name;
private String email;
//域账号
private String username;
//其余属性省略....
}
3、自定义JwtAuthenticationToken类继承UsernamePasswordAuthenticationToken
public class JwtAuthenticatioToken extends UsernamePasswordAuthenticationToken {
private static final long serialVersionUID = 1L;
private String token;
public JwtAuthenticatioToken(Object principal, Object credentials){
super(principal, credentials);
}
public JwtAuthenticatioToken(Object principal, Object credentials, String token){
super(principal, credentials);
this.token = token;
}
public JwtAuthenticatioToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities, String token) {
super(principal, credentials, authorities);
this.token = token;
}
public String getToken(){
return token;
}
public void setToken(String token){
this.token = token;
}
public static long getSerialVersionUID(){
return serialVersionUID;
}
}
4、自定义JwtUserDetails类继承userdetails包下的User类,其中User类实现了UserDetails
public class JwtUserDetails extends User {
private static final long serialVersionUID = 1L;
private UserInfo userInfo;
public JwtUserDetails(String username, String password, Collection<? extends GrantedAuthority> authorities, UserInfo userInfo) {
this(username, password, true,true,true,true,authorities);
this.userInfo = userInfo;
}
public JwtUserDetails(String username, String password, boolean enabled, boolean accountNonExpired,
boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
}
public UserInfo getUserInfo(){
return userInfo;
}
public void setUserInfo(UserInfo userInfo){
this.userInfo = userInfo;
}
}
5、自定义权限封装类GrantedAuthorityImpl实现GrantedAuthority接口
//权限封装
public class GrantedAuthorityImpl implements GrantedAuthority, Serializable {
private static final long serialVersionUID = 1L;
private String authority;
public GrantedAuthorityImpl(String authority){
this.authority = authority;
}
public void setAuthority(String authority){
this.authority = authority;
}
@Override
public String getAuthority() {
return this.authority;
}
}
6、自定义bean UserDetailsServiceImpl实现UserDetailsService接口,并且将此bean命名为@Service(“userDetailsService”)
//实现loadUserByUsername方法
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//获取userInfo用户信息
UserInfo userVo=new UserInfo();
//资源列表
List<String> resourceVos = new ArrayList<>();
//TODO 查询操作,获取资源,用户信息
//生成GrantedAuthority权限列表
List<GrantedAuthority> grantedAuthorities = resourceVos.stream().map(GrantedAuthorityImpl::new).collect(Collectors.toList());
BCryptPasswordEncoder s= new BCryptPasswordEncoder();
userVo.setPermissionCodes(resourceVos);
userVo.setUsername(username);
userVo.setRoleDtoList(externalRoleDtos);
userVo.setCompanyDtoList(externalCompanyDtoList);
userVo.setSystemCodes(systemCodes);
//生成JwtUserDetails对象
return new JwtUserDetails(username, s.encode("888888"), grantedAuthorities,userVo);
}
7、自定义JwtAuthenticationProvider类继承DaoAuthenticationProvider作为身份验证提供者
public class JwtAuthenticationProvider extends DaoAuthenticationProvider {
public JwtAuthenticationProvider(UserDetailsService userDetailsService) {
setUserDetailsService(userDetailsService);
setPasswordEncoder(new BCryptPasswordEncoder());
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 可以在此处覆写整个登录认证逻辑
return super.authenticate(authentication);
}
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
// 可以在此处覆写密码验证逻辑
super.additionalAuthenticationChecks(userDetails, authentication);
}
}
8、自定义JwtAuthenticationFilter类继承BasicAuthenticationFilter进行登录认证检查(过滤器)
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
@Autowired
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException, FilterAuthException {
//异常捕获--重定向到controller(filterException)
try {
SecurityUtils.checkAuthentication(request);
}catch (FilterAuthException e){
request.setAttribute("filterException",e);
request.getRequestDispatcher("/filterException").forward(request,response);
}
chain.doFilter(request, response);
}
}
9、自定义过滤器异常转发controller–FilterExceptionController
@RestController
public class FilterExceptionController {
@RequestMapping("/filterException")
public void filterException(HttpServletRequest request, HttpServletResponse response)throws FilterAuthException{
if (request.getAttribute("filterException") instanceof FilterAuthException){
//响应状态码
response.setStatus(400);
throw (FilterAuthException) request.getAttribute("filterException");
}
}
}
10、配置SecurityConfig
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and().csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS,"/**").permitAll()
.antMatchers("/", "/index.html", "/favicon.ico", "/js/**", "/css/**", "/img/**", "/fonts/**").permitAll()
.antMatchers("/*.html", "/webjars/**", "/swagger-ui.html/**","/v2/api-docs", "/swagger-resources", "/swagger-resources/**").permitAll()
.antMatchers("/api/login").permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/v1/logout/**").permitAll()
.antMatchers("/api/**").hasRole("USER")
.anyRequest().authenticated();
//退出登录处理器
http.logout().logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler());
//访问控制时登录状态检查过滤器
http.addFilterBefore(new JwtAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
}
//认证器,将自定义用户源放入到spring security配置中
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// 使用自定义身份验证组件
UserDetailsService userDetailsService= (UserDetailsService) SpringContextUtils.getBean("userDetailsService");
auth.authenticationProvider(new JwtAuthenticationProvider(userDetailsService));
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
11、定义登录认证类SecurityUtils
public class SecurityUtils {
public static JwtAuthenticatioToken login(HttpServletRequest request, String username, String password, AuthenticationManager authenticationManager){
JwtAuthenticatioToken token = new JwtAuthenticatioToken(username,password);
token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
//执行登录认证过程
Authentication authentication = authenticationManager.authenticate(token);
//认证成功存储认证信息到上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
//生成令牌并返回给客户端
token.setToken(JwtTokenUtils.generateToken(authentication));
return token;
}
//获取令牌进行认证
public static void checkAuthentication(HttpServletRequest request) throws FilterAuthException {
// 获取令牌并根据令牌获取登录认证信息
Authentication authentication = JwtTokenUtils.getAuthenticationFromToken(request);
// 设置登录认证信息到上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
}
//获取当前用户名
public static String getUsername(){
String username = null;
Authentication authentication = getAuthentication();
if (authentication != null){
Object principal = authentication.getPrincipal();
if (principal != null && principal instanceof UserDetails){
username = ((UserDetails) principal).getUsername();
}
}
return username;
}
//获取用户名
public static String getUsername(Authentication authentication){
String username = null;
if (authentication != null){
Object principal = authentication.getPrincipal();
if (principal != null && principal instanceof UserDetails){
username = ((JwtUserDetails) principal).getUsername();
}
}
return username;
}
//获取当前登录信息
public static Authentication getAuthentication() {
if (SecurityContextHolder.getContext() == null){
return null;
}
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication;
}
//获取姓名
public static String getName(){
String name = null;
Authentication authentication = getAuthentication();
if (authentication != null){
Object principal = authentication.getPrincipal();
if (principal != null && principal instanceof UserDetails){
name = ((JwtUserDetails) principal).getUserInfo().getName();
}
}
return name;
}
}
12、定义jwt工具类JwtTokenUtils
@Component
public class JwtTokenUtils implements Serializable {
private static final long serialVersionUID = 1L;
//用户名称
private static final String USERNAME = Claims.SUBJECT;
//创建时间
private static final String CREATED = "created";
//密钥
private static final String jwtSECRET = "JWTSecretKey";
@Value("${mds.reids.base.key}")
private String mdsReidsBaseKeyTemp;
@Value("${ccms.redis.base.user.key}")
private String ccmsReidsBaseUserKeyTemp;
@Value("${mds.redis.base.auth.project.key}")
private String mdsRedisBaseAuthProjectKeyTemp;
private static String mdsReidsBaseKey;
private static String ccmsRedisBaseUserKey;
private static String mdsRedisBaseAuthProjectKey;
@Value("#{'${white.list:}'.empty ? null : '${white.list:}'.split(',')}")
private List<String> whiteListTemp;
private static List<String> whiteList;
@PostConstruct
public void init() {
mdsReidsBaseKey = mdsReidsBaseKeyTemp;
ccmsRedisBaseUserKey = ccmsReidsBaseUserKeyTemp;
mdsRedisBaseAuthProjectKey = mdsRedisBaseAuthProjectKeyTemp;
whiteList=whiteListTemp;
}
//生成令牌
public static String generateToken(Authentication authentication){
Map<String,Object> claims = new HashMap<>(2);
claims.put(USERNAME,SecurityUtils.getUsername(authentication));
claims.put(CREATED,new Date());
return generateToken(claims);
}
//从数据声明中生成令牌
private static String generateToken(Map<String,Object> claims){
return Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS256,jwtSECRET).compact();
}
//根据请求令牌获取登录认证信息
public static Authentication getAuthenticationFromToken(HttpServletRequest request)throws FilterAuthException{
Authentication authentication = null;
//获取请求携带的令牌
String token = JwtTokenUtils.getToken(request);
if (token != null){
if(!RedisUtil.hasKey(mdsReidsBaseKey+getUsernameFromToken(token))){
throw new FilterAuthException("400","登录信息已过期,请重新登录");
}
//判断缓存数据
if(!judgeRedisValue(token)){
//Todo 加白名单,方便开发
if(getUsernameFromToken(token)!=null &&!whiteList.contains(getUsernameFromToken(token))){
return null;
}
}
//请求令牌不能为空
if (SecurityUtils.getAuthentication()==null){
//上下文中Authentication为空
Claims claims = getClaimsFromToken(token);
if (claims == null){
return null;
}
String username = claims.getSubject();
if (username == null){
return null;
}
UserInfo userInfo = getUserInfoFromRedis(username);
if (userInfo == null){
return null;
}else {
//重新生成authentication对象
//权限列表
List<GrantedAuthority> authorities = userInfo.getPermissionCodes().parallelStream().map(GrantedAuthorityImpl::new).collect(Collectors.toList());
BCryptPasswordEncoder s= new BCryptPasswordEncoder();
UserDetails userDetails = new JwtUserDetails(username, s.encode("888888"), authorities,userInfo);
authentication = new JwtAuthenticatioToken(userDetails,null,authorities,token);
}
}else {
if (validateToken(token,SecurityUtils.getUsername())){
// 如果上下文中Authentication非空,且请求令牌合法,直接返回当前登录认证信息
authentication = SecurityUtils.getAuthentication();
}
}
}
return authentication;
}
//获取redis中的用户
private static UserInfo getUserInfoFromRedis(String username){
UserInfo userInfo = new UserInfo();
String userKey = ccmsRedisBaseUserKey + username;
if(RedisUtil.hasKey(userKey)){
String value = (String) RedisUtil.get(userKey);
userInfo = JSON.parseObject(value,UserInfo.class);
}
return userInfo;
}
//验证令牌
private static boolean validateToken(String token, String username) {
String userName = getUsernameFromToken(token);
if (StringUtils.isEmpty(userName)){
return false;
}
return (userName.equals(username));
}
// 从令牌中获取用户名
public static String getUsernameFromToken(String token) {
String username;
try {
Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
//判断令牌是否过期
public static boolean isTokenExpired(String token) {
try {
Claims claims = getClaimsFromToken(token);
Date expiration = claims.getExpiration();
return expiration.before(new Date());
}catch (Exception e){
return false;
}
}
//从令牌中获取数据声明
private static Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser().setSigningKey(jwtSECRET).parseClaimsJws(token).getBody();
}catch (Exception e){
claims = null;
}
return claims;
}
//获取请求token
public static String getToken(HttpServletRequest request) {
String token = request.getHeader("Authorization");
String tokenHead = "Bearer ";
if (token == null){
token = request.getHeader("token");
}else if (token.contains(tokenHead)){
token = token.substring(tokenHead.length());
}
if ("".equals(token)){
token = null;
}
return token;
}
public static boolean judgeRedisValue(String token) throws FilterAuthException {
String userName = getUsernameFromToken(token);
if(!RedisUtil.hasKey(mdsReidsBaseKey+userName)){
return false;
}
//没有过期且token无效,则是被挤下去(同一账号不允许同时在线)
if(!token.equals(RedisUtil.get(mdsReidsBaseKey+userName)) && !whiteList.contains(userName)){
throw new FilterAuthException("400","您的账号在别处登录,请重新登录");
}
//自定义缓存对象--校验通过延长过期时间
RedisUtil.expire(mdsReidsBaseKey+userName,3600);
RedisUtil.expire(ccmsRedisBaseUserKey+userName,3600);
RedisUtil.expire(mdsRedisBaseAuthProjectKey+userName,3600);
return true;
}
}
13、登录接口loginController
@Autowired
private AuthenticationManager authenticationManager;
//用户名密码校验
//TODO
//系统登录认证
JwtAuthenticatioToken jwtAuthenticatioToken = SecurityUtils.login(req,username,password,authenticationManager);
System.out.println("-------登陆成功----------");
HttpSession hs = req.getSession();
hs.setMaxInactiveInterval(60);
String token = jwtAuthenticatioToken.getToken();
//当前登录信息
Authentication authentication = SecurityUtils.getAuthentication();
RedisUtil.set(mdsRedisBaseKey+SecurityUtils.getUsername(authentication),token,3600);
//保持跟token一致的过期时间
RedisUtil.set(ccmsRedisBaseUserKey+username,JSON.toJSONString(userVo),3600);
RedisUtil.hashSet(mdsRedisBaseAuthProjectKey+username,authProjectCodes);
RedisUtil.expire(mdsRedisBaseAuthProjectKey+username,3600);
14、退出登录时清除上下文信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = SecurityUtils.getUsername(authentication);
LOG.info("Logout user {}", username);
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
SecurityContextHolder.clearContext();
RedisUtil.del(mdsRedisBaseKey+username);
RedisUtil.del(ccmsRedisBaseUserKey+username);
RedisUtil.del(mdsRedisBaseAuthProjectKey+username);
15、权限注解@PreAuthorize使用
在需要权限控制的接口上加上注解@PreAuthorize,并且在GrantedAuthorityImpl对象中配置好资源权限
列表,在访问的接口的时候会进行资源权限的校验。
@PreAuthorize("hasAuthority('v1:targetCost:save')")
public Result test(){
//TODO
return Result.setSuccess(null);
}
16、RedisUtil类
@Component
public class RedisUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(RedisUtil.class);
@Autowired
private RedisTemplate<String, Object> redisTemplateTemp;
private static RedisTemplate<String, Object> redisTemplate;
@PostConstruct
public void init() {
redisTemplate = redisTemplateTemp;
}
// =============================common============================
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
* @return
*/
public static boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
LOGGER.error("获取key的失效时间异常" + e.getMessage(), e);
return false;
}
}
/**
* 根据key 获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public static long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public static boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
LOGGER.error("判断key是否存在异常" + e.getMessage(), e);
return false;
}
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
public static void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public static Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public static boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
LOGGER.error("设置String类型异常" + e.getMessage(), e);
return false;
}
}
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public static boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
LOGGER.error("设置String类型及有效期异常" + e.getMessage(), e);
return false;
}
}
/**
* hash结构存储
* @param key hash的key值
* @param map hash的map集合
* @author hongjx5
* @return {
{
@link boolean}}
*/
public static boolean hashSet(String key, Map map){
boolean result = false;
try {
redisTemplate.opsForHash().putAll(key,map);
result = true;
}catch (Exception e){
e.printStackTrace();
}
return result;
}
/**
* 获取Map中的value值
* @param key redis中的key
* @param filed redis存储的map中的key
* @author hongjx5
* @return {
{
@link Object}}
*/
public static Object hashGet(String key,Object filed){
if (key == null){
return null;
}
if (filed == null){
return null;
}
return redisTemplate.opsForHash().get(key, filed);
}
/**
* 递增
*
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
public static long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
*
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
public static long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
}