Home

注解中@Component和@Bean的区别

两者的目的是一样的,都是注册bean到Spring容器中
1、@Component注解表明一个类会作为组件类,并告知Spring要为这个类创建bean。
2、@Bean注解告诉Spring这个方法将会返回一个对象,这个对象要注册为Spring应用上下文中的bean。通常方法体中包含了最终产生bean实例的逻辑。

区别:
1、@Component(@Controller、@Service、@Repository)通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中。
2、而@Bean注解通常是我们在标有该注解的方法中定义产生这个bean的逻辑。
3、@Component 作用于类,@Bean作用于方法

Spring帮助我们管理Bean分为两个部分

  • 一个是注册Bean(@Component , @Repository , @ Controller , @Service , @Configration),
  • 一个装配Bean(@Autowired , @Resource,可以通过byTYPE(@Autowired)、byNAME(@Resource)的方式获取Bean)。 完成这两个动作有三种方式,一种是使用自动配置的方式、一种是使用JavaConfig的方式,一种就是使用XML配置的方式。

@Compent 作用就相当于 XML配置

@Component
public class Student {

    private String name = "lkm";

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

@Bean 需要在配置类中使用,即类上需要加上@Configuration注解


@Configuration
public class WebSocketConfig {
    @Bean
    public Student student(){
        return new Student();
    }

}

两者都可以通过@Autowired装配

@Autowired
Student student;

那为什么有了@Compent,还需要@Bean呢?
如果你想要将第三方库中的组件装配到你的应用中,在这种情况下,是没有办法在它的类上添加@Component注解的,因此就不能使用自动化装配的方案了,但是我们可以使用@Bean,当然也可以使用XML配置。

Read more

自定义注释

Github源码

package com.javaniuniu.scshorlsweb.system.commons.preventresubmit;

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

/**
 * TODO 阻止重复提交
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface PreventResubmitLock {

    /**
     * redis 锁key的前缀
     *
     * @return redis 锁key的前缀
     */
    String prefix() default "";

    /**
     * 过期秒数,默认为5秒
     *
     * @return 轮询锁的时间
     */
    int expire() default 5;

    /**timeUnit
     * 超时时间单位
     *
     * @return 秒
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;

    /**
     * <p>Key的分隔符(默认 :)</p>
     * <p>生成的Key:N:SO1008:500</p>
     *
     * @return String
     */
    String delimiter() default ":";
}

参考链接

Read more

JAVA8新特性

Set<GrantedAuthority> grantedAuths = new HashSet<GrantedAuthority>();
        List<Role> roles = userMapper.selectRolsByUserId(userOne.getTid());
        if (!ObjectUtils.isEmpty(roles)) {
            grantedAuths.addAll(Lists.transform(roles, (Function<Role, GrantedAuthority>) role -> new SimpleGrantedAuthority("ROLE_" + role.getRoleName())));
        }
@Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        return builder -> builder.serializerByType(LocalDateTime.class, localDateTimeDeserializer());
    }

Read more

SpringSecurity用到的类

  1. User:UserDetails的默认实现(框架提供的),用户可以从自己的数据库中取出此用户的账号,密码,以及相关权限,然后用构造方法填充创建一个User对象即可
  2. UserDetails:Spring Security基础接口,包含某个用户的账号,密码,权限,状态(是否锁定)等信息。只有getter方法,配合 SimpleGrantedAuthority 一起使用
  3. UserDetailsService:用于返回用户相关数据。它有loadUserByUsername()方法,根据username查询用户实体,配合 SimpleGrantedAuthorityUser 一起使用
  4. SimpleGrantedAuthority:用于获取权限来授权/控制访问权限(已授予的权限)
  5. SecurityContextHolder 认证成功后,就把用户信息和拥有的权限都存储在其中
  6. AuthenticationEntryPoint:用来解决匿名用户访问无权限资源时的异常,和 AuthenticationException 配合使用
  7. AccessDeineHandler 用来解决认证过的用户访问无权限资源时的异常,和 AccessDeniedException 配合使用
  8. WebSecurityConfigurerAdapter 是个适配器, 在配置的时候,需要我们自己写个配置类去继承他,然后编写自己所特殊需要的配置

Read more

UserDetailsService

package sc.whorl.system.config.springsecurity.conf;


import com.google.common.base.Function;
import com.google.common.collect.Lists;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import lombok.extern.slf4j.Slf4j;
import sc.whorl.logic.domain.dao.auth.UserMapper;
import sc.whorl.logic.domain.model.auth.Role;
import sc.whorl.logic.domain.model.auth.User;


/***
 *
 * @FileName: CustomUserDetailsService

 * @remark: 配置用户权限认证
 * @explain 当用户登录时会进入此类的loadUserByUsername方法对用户进行验证,验证成功后会被保存在当前回话的principal对象中
 *             系统获取当前登录对象信息方法 WebUserDetails webUserDetails = (WebUserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
 *
 *              异常信息:
 *              UsernameNotFoundException     用户找不到
 *              BadCredentialsException       坏的凭据
 *              AccountExpiredException       账户过期
 *              LockedException               账户锁定
 *              DisabledException             账户不可用
 *              CredentialsExpiredException   证书过期
 *
 *
 */
@Slf4j
@Service("myUserDetailService")
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;



    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("登录用户:" + username);
        //用户用户信息和用户角色
        User user = new User();
        user.setLoginName(username);
        User userOne = userMapper.selectOne(user);
        if (ObjectUtils.isEmpty(userOne)) {
            //后台抛出的异常是:org.springframework.security.authentication.BadCredentialsException: Bad credentials  坏的凭证 如果要抛出UsernameNotFoundException 用户找不到异常则需要自定义重新它的异常
            log.info("登录用户:" + username + " 不存在.");
            throw new UsernameNotFoundException("登录用户:" + username + " 不存在");
        }
        Set<GrantedAuthority> grantedAuths = new HashSet<GrantedAuthority>();
        List<Role> roles = userMapper.selectRolsByUserId(userOne.getTid());
        if (!ObjectUtils.isEmpty(roles)) {
            grantedAuths.addAll(Lists.transform(roles, (Function<Role, GrantedAuthority>) role -> new SimpleGrantedAuthority("ROLE_" + role.getRoleName())));
        }
        org.springframework.security.core.userdetails.User baseUser = new org.springframework.security.core.userdetails.User(userOne.getLoginName(), userOne.getPassWord(),
                grantedAuths);
        return baseUser;
    }

}

Read more

User

简介

UserDetails的默认实现(框架提供的),User。用户可以从自己的数据库中取出此用户的账号,密码,以及相关权限,然后用构造方法填充创建一个User对象即可。 注:实现CredentialsContainer接口是为了在登录成功后,清除用户信息中的密码。(登录成功后会将用户信息存储在SecurityContext中)

源码

public class User implements UserDetails, CredentialsContainer {
    private static final long serialVersionUID = 500L;
    private static final Log logger = LogFactory.getLog(User.class);
    private String password;
    private final String username;
    private final Set<GrantedAuthority> authorities;
    private final boolean accountNonExpired;
    private final boolean accountNonLocked;
    private final boolean credentialsNonExpired;
    private final boolean enabled;

    public User(String username, String password, Collection<? extends GrantedAuthority> authorities) {
        this(username, password, true, true, true, true, authorities);
    }

    public User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
        if (username != null && !"".equals(username) && password != null) {
            this.username = username;
            this.password = password;
            this.enabled = enabled;
            this.accountNonExpired = accountNonExpired;
            this.credentialsNonExpired = credentialsNonExpired;
            this.accountNonLocked = accountNonLocked;
            this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
        } else {
            throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
        }
    }
    //省略部分代码
}

Read more

RequestMatcher(配置忽略url和认证的url)

一句话简介

匹配HttpServletRequest的简单策略接口RequestMatcher,其下定义了matches方法,如果返回是true表示提供的请求与提供的匹配规则匹配,如果返回的是false则不匹配。

RequestMatcher其实现类:

  • AntPathRequestMatcher:重点
  • MvcRequestMatcher:重点
  • RegexRequestMatcher: 根据正则模式进行匹配
  • AnyRequestMatcher

使用场景

可配合过滤器,过滤掉不需要token验证的url

示例

import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.ObjectUtils;

import java.util.List;

import javax.servlet.http.HttpServletRequest;

/***
 *  配置忽略url 和认证的url
 */
public class SkipPathAntMatcher implements RequestMatcher {
    private List<String> pathsToSkip;

    public SkipPathAntMatcher(List<String> pathsToSkip) {
        this.pathsToSkip = pathsToSkip;
    }

    @Override
    public boolean matches(HttpServletRequest request) {
        System.out.println("请求路径-->" + request.getRequestURL());
        if (!ObjectUtils.isEmpty(pathsToSkip)) {
            for (String s : pathsToSkip) {
                AntPathRequestMatcher antPathRequestMatcher = new AntPathRequestMatcher(s);
                if (antPathRequestMatcher.matches(request)) {
                    return true;
                }
            }
        }
        return false;
    }
}

Read more

OncePerRequestFilter示例

一句话简介

springboot中javax.servlet.Filter原生接口的实现;而Spring的OncePerRequestFilter类实际上是一个实现了Filter接口的抽象类。spring对Filter进行了一些封装处理。 
OncePerRequestFilter,顾名思义,它能够确保在一次请求中只通过一次filter,而需要重复的执行。 大家常识上都认为,一次请求本来就只filter一次,为什么还要由此特别限定呢,往往我们的常识和实际的实现并不真的一样,经过一番资料的查阅,此方法是为了兼容不同的web container,也就是说并不是所有的container都入我们期望的只过滤一次,servlet版本不同,执行过程也不同,因此,为了兼容各种不同运行环境和版本,默认filter继承OncePerRequestFilter是一个比较稳妥的选择。

使用场景

jwt认证token

示例

package sc.whorl.system.config.jwt;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import lombok.extern.slf4j.Slf4j;
import sc.whorl.system.config.springsecurity.conf.CustomUserDetailsService;
import sc.whorl.system.config.springsecurity.utils.ErrorCodeEnum;
import sc.whorl.system.utils.redis.RedisUtil;

/***
 *
 * @FileName: JwtAuthenticationTokenFilter
 * @remark: jwt认证token
 * @explain 每次请求接口时 就会进入这里验证token 是否合法
 *             token 如果用户一直在操作,则token 过期时间会叠加    如果超过设置的过期时间未操作  则token 失效 需要重新登录
 *
 */
@Slf4j
@Component
public class JwtAuthenPreFilter extends OncePerRequestFilter {
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private RedisUtil redisUtil;

    /**
     * 防止filter被执行两次
     */
    private static final String FILTER_APPLIED = "__spring_security_JwtAuthenPreFilter_filterApplied";


    @Value("${jwt.header:Authorization}")
    private String tokenHeader;

    @Value("${jwt.tokenHead:Bearer-}")
    private String tokenHead;
    /**
     * 距离快过期多久刷新令牌
     */
    @Value("${jwt.token.subRefresh:#{10*60}}")
    private Long subRefresh;
    // 不需要认证的接口
    @Value("${jwt.security.antMatchers}")
    private String antMatchers;
    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    public JwtAuthenPreFilter() {
    }


    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        if (httpServletRequest.getAttribute(FILTER_APPLIED) != null) {
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }
        httpServletRequest.setAttribute(FILTER_APPLIED, true);

        SkipPathAntMatcher skipPathRequestMatcher = new SkipPathAntMatcher(Arrays.asList(antMatchers.split(",")));
        //过滤掉不需要token验证的url
        if (skipPathRequestMatcher.matches(httpServletRequest)) {
            filterChain.doFilter(httpServletRequest, httpServletResponse);
        } else {
            try {
                //1.判断是否有效 2.判断是否过期 3.如果未过期的,且过期时间小于10分钟的延长过期时间,并在当前response返回新的header,客户端需替换此令牌
                String authHeader = httpServletRequest.getHeader(this.tokenHeader);
                if (authHeader != null && authHeader.startsWith(tokenHead)) {
                    final String authToken = authHeader.substring(tokenHead.length());
                    JWTUserDetail userDetail = jwtTokenUtil.getUserFromToken(authToken);
                    if (ObjectUtils.isEmpty(userDetail)) {
                        log.info("令牌非法,解析失败{}!", authToken);
                        throw new BadCredentialsException(ErrorCodeEnum.TOKEN_INVALID.getMessage());
                    }
                    if (jwtTokenUtil.isTokenExpired(authToken)) {
                        log.info("令牌已失效!{}", authToken);
                        throw new BadCredentialsException(ErrorCodeEnum.TOKEN_INVALID.getMessage());
                    }
                    if (!StringUtils.isEmpty(redisUtil.get(authToken))) {
                        log.info("令牌已位于黑名单!{}", authToken);
                        throw new BadCredentialsException(ErrorCodeEnum.TOKEN_INVALID.getMessage());
                    }
                    //令牌快过期生成新的令牌并设置到返回头中,客户端在每次的restful请求如果发现有就替换原值,同时原值做redis黑名单
                    if (new Date(System.currentTimeMillis() - subRefresh).after(jwtTokenUtil.getExpirationDateFromToken(authToken))) {
                        String resAuthToken = jwtTokenUtil.generateToken(userDetail);
                        redisUtil.setEx(authToken,String.format(JwtTokenUtil.JWT_TOKEN_PREFIX, userDetail.getUserType(), userDetail.getUserId()), subRefresh, TimeUnit.SECONDS);
                        httpServletResponse.setHeader(tokenHeader, tokenHead + resAuthToken);
                    }
                    JwtTokenUtil.LOCAL_USER.set(userDetail);
                    UserDetails userDetails = customUserDetailsService.loadUserByUsername(userDetail.getLoginName());
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }else{
                    //需要校验却无用户token
                    System.out.println("无header请求-->" + httpServletRequest.getRequestURI());
                   // throw new InsufficientAuthenticationException(ErrorCodeEnum.NO_TOKEN.getMessage());
                }
            } catch (Exception e) {
                log.info("令牌解析失败!", e);
                throw new BadCredentialsException(ErrorCodeEnum.TOKEN_INVALID.getMessage());
            }
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            //调用完成后清除
            JwtTokenUtil.LOCAL_USER.remove();
        }
    }
}


Read more