Spring Security 安全认证
欢迎光临我的博客[http://poetize.cn],前端使用Vue2,聊天室使用Vue3,后台使用Spring Boot
Spring Boot 使用 Mybatis
依赖 <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency>使用: 启动类上注解:@MapperScan("com.dao"),或者每个 dao 接口上添加注解:@Mapper @Mapper 是 MyBatis 的注解,目的是为了让 spring 能够根据 xml 和这个接口动态生成接口的实现。 @Mapper public interface UserDao { @Select("select * from user where id=#{id}") User GetUserbyId(@Param("id") Integer id); } 注解方式: @Select 是查询类的注解,所有的查询均使用这个 @Result 修饰返回的结果集,关联实体类属性和数据库字段一一对应,如果实体类属性和数据库属性名保持一致,就不需要这个属性来修饰。 @Insert 插入数据库使用,直接传入实体类会自动解析属性到对应的值 @Update 负责修改,也可以直接传入对象 @Delete 负责删除 mapper.xml 方式: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.dao.UserDao"> <!-- 可根据自己的需求,是否要使用 --> <resultMap type="User" id="UserDaoMap"> <id column="id" property="id" jdbcType="INTEGER" /> <result column="userName" property="useName" jdbcType="VARCHAR" /> </resultMap> <select id="GetUserbyId" parameterType="INTEGER" resultMap="UserDaoMap"> select * from user where 1=1 and id = #{id,jdbcType=INTEGER} </select> </mapper> namespace 指定这个 xml 所对应的是哪个 dao 接口。 select 标签的 id 对应的就是 dao 接口中的方法名,parameterType 就是传进来的参数类型。
Mysql 去掉 ONLY_FULL_GROUP_BY
ONLY_FULL_GROUP_BY: 对于GROUP_BY聚合操作,如果在SELECT中的列既没有在GROUP_BY中出现, 本身也不是聚合列(使用SUM,ANG等修饰的列),那么这句SQL是不合法的,因为那一列是不确定的。SELECT @@global.sql_mode;SET @@global.sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';
Spring Security oAuth2
Spring Security 基于 Servlet 过滤器、IoC 和 AOP,为 Web 请求和方法调用提供身份确认和授权处理,避免了代码耦合,减少了大量重复代码工作。oAuth2 是授权标准,协议,体现在代码上是接口。实现有 Spring Security 和 Shire。Access Token 是客户端访问资源服务器的令牌(Access Token 临时的,有一定有效期)。Refresh Token 的作用是用来刷新 Access Token(Refresh Token 效期非常长)。刷新接口类似于:http://www.funtl.com/refresh?refresh_token=&client_id=&client_secret=
认证:Authentication 授权:Authorization
认证(Authentication):确定一个用户的身份的过程。授权(Authorization):判断一个用户是否有访问某个安全对象的权限。认证与授权中关键的三个过滤器: 1. UseamePasswordAuthenticationFilter: 该过滤器用于拦截我们表单提交的请求(默认为/login),进行用户的认证过程。 2. ExceptionTranslationFilter: 该过滤器主要用来捕获处理 Spring Security 抛出的异常,异常主要来源于 FilterSecurityInterceptor。 3. FilterSecurityInterceptor: 该过滤器主要用来进行授权判断。
Spring Security 核心组件
Spring Security 核心组件有: SecurityContext SecurityContextHolder Authentication Userdetails AuthenticationManagerSecurityContext(安全上下文): 当用户通过认证之后,就会为这个用户生成一个唯一的SecurityContext,里面包含用户的认证信息Authentication。 public interface SecurityContext extends Serializable { Authentication getAuthentication(); void setAuthentication(Authentication var1); }SecurityContextHolder(安全上下文持有者): 在SecurityContextHolder中保存的是当前访问者的信息。Spring Security使用一个Authentication对象来表示这个信息。 缺省情况下,SecurityContextHolder使用了ThreadLocal机制来保存每个使用者的安全上下文。 SecurityContextHolder可以工作在以下三种模式之一: MODE_THREADLOCAL (缺省工作模式) MODE_GLOBAL(所有线程中都相同) MODE_INHERITABLETHREADLOCAL(存储在线程中,但子线程可以获取到父线程中的 SecurityContext) 修改SecurityContextHolder的工作模式有两种方法: 设置一个系统属性(system.properties):spring.security.strategy 调用SecurityContextHolder静态方法:setStrategyName() 存储当前认证信息: SecurityContextHolder.getContext().setAuthentication(token); 获取保存在ThreadLocal中的用户信息: SecurityContextHolder.getContext().getAuthentication().getPrincipal();Authentication(认证): public interface Authentication extends Principal, Serializable { Collection<? extends GrantedAuthority> getAuthorities(); //获取用户权限信息 Object getCredentials(); // 获取认证信息 Object getDetails(); //获取用户描述信息 Object getPrincipal(); //获取用户身份信息,在未认证的情况下获取到的是用户名,在已认证的情况下获取到的是 UserDetails boolean isAuthenticated(); //是否已经认证 void setAuthenticated(boolean var1) throws IllegalArgumentException; }UserDetails public interface UserDetails extends Serializable { Collection<? extends GrantedAuthority> getAuthorities(); String getPassword(); String getUseame(); boolean isAccountNonExpired(); boolean isAccountNonLocked(); boolean isCredentialsNonExpired(); boolean isEnabled(); }UserDetailsService UserDetailsService也是一个接口,且只有一个方法loadUserByUseame,用来获取UserDetails。AuthenticationManager AuthenticationManager 是一个接口,它只有一个方法,接收参数为Authentication,其定义如下: public interface AuthenticationManager { Authentication authenticate(Authentication authentication) throws AuthenticationException; } AuthenticationManager 的作用就是校验 Authentication, 如果验证失败会抛出 AuthenticationException 异常。
pre-post-annotations
Spring Security 中定义了四个支持使用表达式的注解,分别是: @PreAuthorize @PreAuthorize("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')") @PreAuthorize("#user.name.equals('abc')") @PostAuthorize @PostAuthorize 是在方法调用完成后进行权限检查, 它不能控制方法是否能被调用,只能在方法调用完成后检查权限决定是否要抛出 AccessDeniedException。 @PreFilter @PreFilter(filterTarget="ids", value="filterObject%2==0"):保留ids集合中的偶数 @PostFilter 其中前两者可以用来在方法调用前或者调用后进行权限检查, 后两者可以用来对集合类型的参数或者返回值进行过滤。
hasRole 和 hasAuthority
角色授权:授权代码需要加 ROLE_ 前缀,Controller 上使用时不要加前缀。权限授权:设置和使用时,名称保持一至即可。
使用流程
WebSecurityConfig(配置文件)
@Configuration/** * @EnableWebSecurity注解有两个作用: * 1: 加载了WebSecurityConfiguration, 配置安全策略。 * 2: 加载了AuthenticationConfiguration, 配置了认证信息。 */@EnableWebSecurity/** * prePostEnabled = true: * 使用表达式时间方法级别的安全性 * 4个注解可用:@PreAuthorize等 */@EnableGlobalMethodSecurity(prePostEnabled = true)public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserService userService;@Beanpublic JwtTokenFilter authenticationTokenFilterBean() throws Exception {retu new JwtTokenFilter();}/** * 用于认证过程中载入用户信息:public class UserService implements UserDetailsService */@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());}@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception {/** * SpringSecurity会把匹配规则按注册先后顺序放到一个ArrayList<UrlMapping>中, * 先注册的规则放前面,后注册的放后面。 */httpSecurity //禁用csrf.csrf().disable()/** * 有多个配置HttpSecurity的配置类时必须设置.antMatcher("/**"), * 因为不设置的话默认匹配所有,就不会执行到下面的HttpSecurity了。 * * 需要在类上加注解@Order,指明优先级 */.antMatcher("/**")//设置无状态SessionCreationPolicy(不需要Session).sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()//登陆页.formLogin().loginPage("/login") //登陆页.failureUrl("/login")//登陆失败的页面跳转URL.defaultSuccessUrl("/index") //登录成功后的默认处理页.permitAll().and()//注销页.logout() .logoutUrl("/logout") //注销接口 .logoutSuccessUrl("/login") //注销成功跳转接口 .permitAll() .and()//通过 authorizeRequests() 定义哪些URL需要被保护、哪些不需要被保护.authorizeRequests()//OPTIONS请求全部放行.antMatchers(HttpMethod.OPTIONS).permitAll()//其他 post put delete get 请求全部拦截校验.antMatchers(HttpMethod.POST).authenticated()//需要认证.antMatchers(HttpMethod.PUT).authenticated().antMatchers(HttpMethod.DELETE).authenticated().antMatchers(HttpMethod.GET).authenticated();/** * addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter) * 在 beforeFilter 之前添加 filter。 * * UseamePasswordAuthenticationFilter:登陆用户密码验证过滤器 */httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UseamePasswordAuthenticationFilter.class);/** * 禁用默认响应安全头,添加要用的响应安全头: * httpSecurity.headers().defaultsDisabled().cacheControl(); */httpSecurity.headers().cacheControl();}}
JwtTokenFilter过滤器
每次请求都经过过滤器,判断是否持有Token(已经登陆),如果持有Token就进行认证,否则以游客身份认证。
@Componentpublic class JwtTokenFilter extends OncePerRequestFilter {@Autowiredprivate UserService userService;@Autowiredprivate JwtConfig jwtConfig;@Overrideprotected void doFilterInteal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {boolean giveFlag = false;//从请求头中获取tokenString authHeader = request.getHeader(jwtConfig.getHeader());//认证if (authHeader != null && authHeader.startsWith(jwtConfig.getPrefix())) {//如果token存在,并且格式正确,就从数据库查询UserDetailsUserDetails userDetails = userService.loadUserByToken(authHeader);if (null != userDetails) {if (SecurityContextHolder.getContext().getAuthentication() == null) { //用户存在,本次会话的权限还未被写入//生成认证信息UseamePasswordAuthenticationToken authentication =new UseamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));//将认证信息写入本次会话SecurityContextHolder.getContext().setAuthentication(authentication);}} else {//用户不存在,以游客身份认证giveFlag = true;}} else {//token不存在或者格式错误,以游客身份认证giveFlag = true;}//给游客授权if (giveFlag) {List<SimpleGrantedAuthority> authorities = new ArrayList<>();authorities.add(new SimpleGrantedAuthority("NORMAL"));User user = new User("NORMAL", "NORMAL", authorities);UseamePasswordAuthenticationToken authentication =new UseamePasswordAuthenticationToken(user, null, user.getAuthorities());SecurityContextHolder.getContext().setAuthentication(authentication);}chain.doFilter(request, response);}}
UserService
@Servicepublic class UserService implements UserDetailsService {@Autowiredprivate UserDao userDao;@Autowiredprivate RoleDao roleDao;@Autowiredprivate HttpServletRequest request;@Autowiredprivate BCryptPasswordEncoder encoder;@Autowiredprivate JwtTokenUtil jwtTokenUtil;@Autowiredprivate JwtConfig jwtConfig;@Autowiredprivate RedisTemplate<String, String> redisTemplate;/** * 登录(校验User,通过校验则生成Token并放入Redis,并返回Token) */public String login(User user) {User dbUser = userDao.findUserByName(user.getName());//用户名或密码错误if (null == dbUser || !encoder.matches(user.getPassword(), dbUser.getPassword())) {throw new UseameNotFoundException("用户名或密码错误");}//用户已被封禁if (0 == dbUser.getState()) {throw new RuntimeException("你已被封禁");}//生成Tokenfinal UserDetails userDetails = this.loadUserByUseame(user.getName());final String token = jwtTokenUtil.generateToken(userDetails);//key:TOKEN_useame,value:Bearer tokenredisTemplate.opsForValue().set(JwtConfig.REDIS_TOKEN_KEY_PREFIX + user.getName(), jwtConfig.getPrefix() + token, jwtConfig.getTime(), TimeUnit.SECONDS);retu token;}@Overridepublic UserDetails loadUserByUseame(String name) throws UseameNotFoundException {User user = userDao.findUserByName(name);List<SimpleGrantedAuthority> authorities = new ArrayList<>(1);List<Role> roles = roleDao.findUserRoles(user.getId());for (Role role : roles) {authorities.add(new SimpleGrantedAuthority(role.getName()));}retu new org.springframework.security.core.userdetails.User(user.getName(), null, authorities);}/** * 从Token中提取信息,校验Token */public UserDetails loadUserByToken(String authHeader) { //除去前缀,获取Tokenfinal String authToken = authHeader.substring(jwtConfig.getPrefix().length());String useame = jwtTokenUtil.getUseameFromToken(authToken);//Token非法if (null == useame) {retu null;}//通过useame从缓存中获取Token:判断Token是否过期,或者Token是否匹配String redisToken = redisTemplate.opsForValue().get(JwtConfig.REDIS_TOKEN_KEY_PREFIX + useame);if (!authHeader.equals(redisToken)) {retu null;}List<String> roles = jwtTokenUtil.getRolesFromToken(authToken);List<SimpleGrantedAuthority> authorities = new ArrayList<>();for (String role : roles) {authorities.add(new SimpleGrantedAuthority(role));}retu new org.springframework.security.core.userdetails.User(useame, null, authorities);}/** * 退出登录(清除Redis缓存中的Token) */public void logout() {String useame = jwtTokenUtil.getUseameFromRequest(request);redisTemplate.delete(JwtConfig.REDIS_TOKEN_KEY_PREFIX + useame);}}
JwtTokenUtil
public interface Claims extends Map<String, Object>, ClaimsMutator<Claims> {String ISSUER = "iss";String SUBJECT = "sub";String AUDIENCE = "aud";String EXPIRATION = "exp";String NOT_BEFORE = "nbf";String ISSUED_AT = "iat";String ID = "jti";}@ConfigurationProperties(prefix = "jwt")@Component@Datapublic class JwtConfig {public static final String REDIS_TOKEN_KEY_PREFIX = "TOKEN_"; //Redis缓存前缀private long time = 432000; //5天过期时间private String secret = "Sara"; //JWT密码private String prefix = "Bearer "; //Token前缀private String header = "Authorization"; //存放Token的Header Key}@Componentpublic class JwtTokenUtil implements Serializable {private static final long serialVersionUID = -5625635588908941275L;private static final String CLAIM_KEY_USERNAME = "sub";private static final String CLAIM_KEY_CREATED = "created";private static final String CLAIM_KEY_ROLES = "roles";@Autowiredprivate JwtConfig jwtConfig;/** * 生成token */public String generateToken(UserDetails userDetails) {Map<String, Object> claims = new HashMap<>(3);claims.put(CLAIM_KEY_USERNAME, userDetails.getUseame()); //放入用户名 subclaims.put(CLAIM_KEY_CREATED, new Date()); //放入token生成时间 createdList<String> roles = new ArrayList<>();Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();for (GrantedAuthority authority : authorities) {roles.add(authority.getAuthority());}claims.put(CLAIM_KEY_ROLES, roles); //放入用户权限 rolesretu generateToken(claims);}/** * 生成token */private String generateToken(Map<String, Object> claims) {retu Jwts.builder().setClaims(claims).setExpiration(new Date(System.currentTimeMillis() + jwtConfig.getTime() * 1000)).signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret()).compact();}/** * 校验token */public Boolean validateToken(String token, UserDetails userDetails) {final String useame = getUseameFromToken(token); //从token中取出用户名retu (useame.equals(userDetails.getUseame())&&!isTokenExpired(token) //校验是否过期);}/** * 从request中获取useame */public String getUseameFromRequest(HttpServletRequest request) {String token = request.getHeader(jwtConfig.getHeader());token = token.substring(jwtConfig.getPrefix().length());retu token == null ? null : getUseameFromToken(token);}/** * 从token中获取useame */public String getUseameFromToken(String token) {String useame;try {final Claims claims = getClaimsFromToken(token);useame = claims.getSubject();} catch (Exception e) {useame = null;}retu useame;}/** * 从token中获取Claims */private Claims getClaimsFromToken(String token) {Claims claims;try {claims = Jwts.parser().setSigningKey(jwtConfig.getSecret()).parseClaimsJws(token).getBody();} catch (Exception e) {claims = null;}retu claims;}/** * 从token中获取创造时间 */public Date getCreatedDateFromToken(String token) {Date created;try {final Claims claims = getClaimsFromToken(token);created = new Date((Long)claims.get(CLAIM_KEY_CREATED));} catch (Exception e) {created = null;}retu created;}/** * token是否过期 * * true 过期 false 未过期 */public Boolean isTokenExpired(String token) {final Date expiration = getExpirationDateFromToken(token);boolean isExpired = expiration.before(new Date());retu isExpired;}/** * 从token中获取过期时间 */public Date getExpirationDateFromToken(String token) {Date expiration;try {final Claims claims = getClaimsFromToken(token);expiration = claims.getExpiration();} catch (Exception e) {expiration = null;}retu expiration;}/** * 从token中获取用户角色 */public List<String> getRolesFromToken(String authToken) {List<String> roles;try {final Claims claims = getClaimsFromToken(authToken);roles = (List<String>)claims.get(CLAIM_KEY_ROLES);} catch (Exception e) {roles = null;}retu roles;}}
作者:LittleDonkey
来源链接:https://www.cnblogs.com/loveer/p/12081100.html
版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。
2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。