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