Home

对返回给前端的数据进行格式封装处理-SpringBoot

package com.javaniuniu.scshorlsweb.system.commons;

import java.io.Serializable;

public class MsgResponseBody<T> implements Serializable {
    private static final String SUCCESS = "0";
    private static final String ERROR = "0";
    private String resultCode;
    private T result;

    private MsgResponseBody(String statusCode) {
        this.resultCode = statusCode;
    }

    public static MsgResponseBody success() {
        return new MsgResponseBody("0");
    }

    public static MsgResponseBody error() {
        return new MsgResponseBody("1");
    }

    public static MsgResponseBody error(String errorCode) {
        return new MsgResponseBody(errorCode);
    }

    public String getResultCode() {
        return resultCode;
    }

    public MsgResponseBody setResultCode(String resultCode) {
        this.resultCode = resultCode;
        return this;
    }

    public T getResult() {
        return result;
    }

    public MsgResponseBody setResult(T result) {
        this.result = result;
        return this;
    }
}

Read more

WebSecurityConfigurerAdapter

WebSecurityConfigurerAdapter 类是个适配器, 在配置的时候,需要我们根据自己的业务写个配置类去继承他

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


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.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.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;

import sc.whorl.system.config.jwt.JwtAuthenPreFilter;
import sc.whorl.system.config.springsecurity.handler.UnauthorizedHandler;


/***
 *
 * @FileName: WebSecurityConfig
 * @remark: web 安全性配置
 * @explain 当用户登录时会进入此类的loadUserByUsername方法对用户进行验证,验证成功后会被保存在当前回话的principal对象中
 *             系统获取当前登录对象信息方法 WebUserDetails webUserDetails = (WebUserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
 *
 */

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
        //@Secured("ROLE_ADMIN")
        securedEnabled = true,
        //@RolesAllowed("ROLE_ADMIN")
        jsr250Enabled = true,
        //@PreAuthorize("hasRole('ROLE_USER')")
        prePostEnabled = true
)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    // 不需要认证的接口
    @Value("${jwt.security.antMatchers}")
    private String antMatchers;

    /**
     * 置user-detail服务
     * 方法描述
     * accountExpired(boolean)                定义账号是否已经过期
     * accountLocked(boolean)                 定义账号是否已经锁定
     * and()                                  用来连接配置
     * authorities(GrantedAuthority...)       授予某个用户一项或多项权限
     * authorities(List)                      授予某个用户一项或多项权限
     * authorities(String...)                 授予某个用户一项或多项权限
     * disabled(boolean)                      定义账号是否已被禁用
     * withUser(String)                       定义用户的用户名
     * password(String)                       定义用户的密码
     * roles(String...)                       授予某个用户一项或多项角色
     *
     * @param auth
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //设置UserDetailsService
        auth.userDetailsService(customUserDetailsService)
                //使用BCrypt进行密码的hash
                .passwordEncoder(bCryptPasswordEncoder());
    }

    /**
     * 声明AuthenticationManager
     *
     * @return
     */
    @Bean(BeanIds.AUTHENTICATION_MANAGER)
    public AuthenticationManager customAuthenticationManager() throws Exception {
        return authenticationManager();
    }

    /**
     * 密码加密方式
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 配置如何通过拦截器保护请求
     * 指定哪些请求需要认证,哪些请求不需要认证,以及所需要的权限
     * 通过调用authorizeRequests()和anyRequest().authenticated()就会要求所有进入应用的HTTP请求都要进行认证
     *
     * 方法描述
     * anonymous()                                        允许匿名用户访问
     * authenticated()                                    允许经过认证的用户访问
     * denyAll()                                          无条件拒绝所有访问
     * fullyAuthenticated()                如果用户是完整的话(不是通过Remember-me功能认证的),就允许访问
     * hasAnyAuthority(String...)                 如果用户具备给定权限中的某一个的话,就允许访问
     * hasAnyRole(String...)                    如果用户具备给定角色中的某一个的话,就允许访问
     * hasAuthority(String)                     如果用户具备给定权限的话,就允许访问
     * hasIpAddress(String)                    如果请求来自给定IP地址的话,就允许访问
     * hasRole(String)                        如果用户具备给定角色的话,就允许访问
     * not()                               对其他访问方法的结果求反
     * permitAll()                           无条件允许访问
     * rememberMe()                          如果用户是通过Remember-me功能认证的,就允许访问
     *
     * @param http
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //关闭csrf验证
        http.cors().and().csrf().disable()
                // 基于token,所以不需要session,此处策略为不需要创建
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                //对请求进行认证  url认证配置顺序为:1.先配置放行不需要认证的 permitAll() 2.然后配置 需要特定权限的 hasRole() 3.最后配置 anyRequest().authenticated()
                .authorizeRequests().antMatchers("/",
                "/favicon.ico",
                "/**/*.png",
                "/**/*.gif",
                "/**/*.svg",
                "/**/*.jpg",
                "/**/*.html",
                "/**/*.css",
                "/**/*.js","/swagger-resources/**","/webjars/springfox-swagger-ui/**","/v2/api-docs/**")
                .permitAll()
                // 所有 antMatchers配置的 请求的都放行 不做认证即不需要登录即可访问,可以配置登陆下载等不需要token的请求路径
                .antMatchers(antMatchers.split(",")).permitAll()
                .antMatchers(HttpMethod.GET, "/api/download/**")
                .permitAll().antMatchers(HttpMethod.OPTIONS).permitAll()//跨域请求会先进行一次options请求
                // 其他请求都需要进行认证,认证通过够才能访问   待考证:如果使用重定向 httpServletRequest.getRequestDispatcher(url).forward(httpServletRequest,httpServletResponse); 重定向跳转的url不会被拦截(即在这里配置了重定向的url需要特定权限认证不起效),但是如果在Controller 方法上配置了方法级的权限则会进行拦截
                .anyRequest().authenticated()
                .and().exceptionHandling()
                // 认证配置当用户请求了一个受保护的资源,但是用户没有通过登录认证,则抛出登录认证异常,MyAuthenticationEntryPointHandler类中commence()就会调用
                .authenticationEntryPoint(myAuthenticationEntryPoint());


        // 添加JWT filter 验证其他请求的Token是否合法
        http.addFilterBefore(jwtAuthenPreFilterBean(), FilterSecurityInterceptor.class);
        // 禁用缓存
        http.headers().cacheControl();


    }


    /**
     * 登录认证异常
     *
     * @return
     */
    @Bean
    public AuthenticationEntryPoint myAuthenticationEntryPoint() {
        return new UnauthorizedHandler();
    }

    /**
     * 注册jwt 过滤器
     */
    @Bean
    public JwtAuthenPreFilter jwtAuthenPreFilterBean() throws Exception {

        return new JwtAuthenPreFilter();
    }

}

Read more

UserDetails

简介

  • UserDetails => Spring Security基础接口,包含某个用户的账号,密码,权限,状态(是否锁定)等信息。只有getter方法。
  • Authentication => 认证对象,认证开始时创建,认证成功后存储于SecurityContext
  • principal => 用户信息对象,是一个Object,通常可转为UserDetails

    UserDetails接口

    用于表示一个principal,但是一般情况下是作为(你所使用的用户数据库)和(Spring Security 的安全上下文需要保留的信息)之间的适配器。

实际上就是相当于定义一个规范,Security这个框架不管你的应用时怎么存储用户和权限信息的。只要你取出来的时候把它包装成一个UserDetails对象给我用就可以了。

package org.springframework.security.core.userdetails;

import java.io.Serializable;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;

public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
}

UserDetails用来做什么?为什么还要带上权限集合?

如果我们不用认证框架,我们是怎么手动实现登录认证的?

基本上就是根据前端提交上来的用户名从数据库中查找这个账号的信息,然后比对密码。再进一步,可能还会添加一个字段来判断,当前用户是否已被锁定。这个接口就是这么用的。即把这些信息取出来,然后包装成一个对象交由框架去认证。

为什么还要带上权限?

因为登录成功后也不是什么都能访问的,还要根据你所拥有的权限进行判断。有权限你才能访问特定的对象。Security框架是这样设计的,即认证成功后,就把用户信息和拥有的权限都存储在SecurityContext中,当访问受保护资源(某个对象/方法)的时候,就把权限拿出来比对,看看是否满足。

框架提供的UserDetails默认实现

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");
        }
    }
    //省略部分代码
}

什么时候提供UserDetails信息,怎么提供?

UserDetailsService接口

那肯定是认证的时候。其实认证的操作,框架都已经帮你实现了,它所需要的只是,你给我提供获取信息的方式。所以它就定义一个接口,然后让你去实现,实现好了之后再注入给它。

框架提供一个UserDetailsService接口用来加载用户信息。如果要自定义实现的话,用户可以实现一个CustomUserDetailsService的类,然后把你的应用中的UserService和AuthorityService注入到这个类中,用户获取用户信息和权限信息,然后在loadUserByUsername方法中,构造一个User对象(框架的类)返回即可。

package org.springframework.security.core.userdetails;

public interface UserDetailsService {
    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}

框架提供的UserDetailsService接口默认实现

  • InMemoryDaoImpl => 存储于内存
  • JdbcDaoImpl => 存储于数据库(磁盘) 其中,如果你的数据库设计符合JdbcDaoImpl中的规范,你也就不用自己去实现UserDetailsService了。但是大多数情况是不符合的,因为你用户表不一定就叫users,可能还有其他前缀什么的,比如叫tb_users。或者字段名也跟它不一样。如果你一定要使用这个JdbcDaoImpl,你可以通过它的setter方法修改它的数据库查询语句。

注:它是利用Spring框架的JdbcTemplate来查询数据库的

public class JdbcDaoImpl extends JdbcDaoSupport implements UserDetailsService, MessageSourceAware {
    public static final String DEF_USERS_BY_USERNAME_QUERY = "select username,password,enabled from users where username = ?";
    public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY = "select username,authority from authorities where username = ?";
    public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY = "select g.id, g.group_name, ga.authority from groups g, group_members gm, group_authorities ga where gm.username = ? and g.id = ga.group_id and g.id = gm.group_id";
    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
    private String authoritiesByUsernameQuery = "select username,authority from authorities where username = ?";
    private String groupAuthoritiesByUsernameQuery = "select g.id, g.group_name, ga.authority from groups g, group_members gm, group_authorities ga where gm.username = ? and g.id = ga.group_id and g.id = gm.group_id";
    private String usersByUsernameQuery = "select username,password,enabled from users where username = ?";
    private String rolePrefix = "";
    private boolean usernameBasedPrimaryKey = true;
    private boolean enableAuthorities = true;
    private boolean enableGroups;
    //省略方法
}

注入到哪里去呢?

那肯定是注入到认证处理类中的,框架利用AuthenticationManager(接口)来进行认证。而Security为了支持多种方式认证,它提供ProviderManager类,这个实现了AuthenticationManager接口。它拥有多种认证方式,可以根据认证的类型委托给对应的认证处理类进行处理,这个处理类实现了AuthenticationProvider接口。

所以,最终UserDetailsService是注入到AuthenticationProvider的实现类中。

误解

  1. UserDetailService 负责认证用户 实际上:UserDetailService只单纯地负责存取用户信息,除了给框架内的其他组件提供数据外没有其他功能。而认证过程是由AuthenticationManager来完成的。(大多数情况下,可以通过实现AuthenticationProvider接口来自定义认证过程)

Read more

SecurityContext使用实例

默认实现SecurityContextHolder

  • SecurityContext:认证成功后,就把用户信息和拥有的权限都存储在其中
  • Authentication => 认证对象,认证开始时创建,认证成功后存储于SecurityContext
  • principal => 用户信息对象,是一个Object,通常可转为UserDetails

Authentication中的principal方法获取对象

@Controller
public class WsController {
    @Autowired
    SimpMessagingTemplate simpMessagingTemplate;

    @MessageMapping("/ws/chat")
    public void handleMsg(Authentication authentication, ChatMsg chatMsg) {
        Hr hr = (Hr) authentication.getPrincipal();
        chatMsg.setFrom(hr.getUsername());
        chatMsg.setFromNickname(hr.getName());
        chatMsg.setDate(new Date());
        simpMessagingTemplate.convertAndSendToUser(chatMsg.getTo(), "/queue/chat", chatMsg);
    }
}
public class HrUtils {
    public static Hr getCurrentHr() {
        return ((Hr) SecurityContextHolder.getContext().getAuthentication().getPrincipal());
    }
}

SecurityContext

/**
     * 用户登陆相关,主要验证用户用户密码以及设置jwt和用户缓存相关,并返回jwt的凭证
     *
     * @param userVo
     * @return
     */
    public MsgResponseBody<JWTUserDetail> login(UserVo userVo) {
        User user = new User();
        user.setLoginName(userVo.getAccountname());
        User userOne = selectOne(user);
        if (ObjectUtils.isEmpty(userOne)) {
            log.info("用户不存在!");
            return MsgResponseBody.error(ErrorCodeEnum.LOGIN_INCORRECT.getCode()).setResult(ErrorCodeEnum.LOGIN_INCORRECT.getMessage());
        }
        if (!bCryptPasswordEncoder.matches(userVo.getPassword(), userOne.getPassWord())) {
            log.info("用户登陆密码错误!");
            return MsgResponseBody.error(ErrorCodeEnum.LOGIN_INCORRECT.getCode()).setResult(ErrorCodeEnum.LOGIN_INCORRECT.getMessage());
        }
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        userVo.getAccountname(),
                        userVo.getPassword()
                )
        );
        SecurityContextHolder.getContext().setAuthentication(authentication);
        //使用jwt生成token 用于权限效验
        JWTUserDetail jwtUserDetail = new JWTUserDetail();
        jwtUserDetail.setLoginName(userOne.getLoginName());
        jwtUserDetail.setLoginTime(new Date());
        jwtUserDetail.setUserId(userOne.getTid());
        jwtUserDetail.setUserType(JWTUserDetail.UserType.User);
        String token = jwtTokenUtil.generateToken(jwtUserDetail);
        jwtUserDetail.setJwtToken(token);
        return MsgResponseBody.success().setResult(jwtUserDetail);
    }

参考文档

Read more

SecurityContextHolder

作用: 保留系统当前的安全上下文细节,其中就包括当前使用系统的用户的信息。

上下文细节怎么表示?
用SecurityContext对象来表示

每个用户都会有它的上下文,那这个SecurityContext保存在哪里呢?
存储在一个SecurityContextHolder中,整个应用就一个SecurityContextHolder。

SecurityContextHolder存储SecurityContext的方式?
这要考虑到应用场景。
1.单机系统,即应用从开启到关闭的整个生命周期只有一个用户在使用。由于整个应用只需要保存一个SecurityContext(安全上下文即可)
2.多用户系统,比如典型的Web系统,整个生命周期可能同时有多个用户在使用。这时候应用需要保存多个SecurityContext(安全上下文),需要利用ThreadLocal进行保存,每个线程都可以利用ThreadLocal获取其自己的SecurityContext,及安全上下文。

源码分析:

SecurityContextHolder结构

图片pic1

SecurityContextHolder.java(部分源码) 图片pic2

由源码可知,SecurityContextHolder利用了一个SecurityContextHolderStrategy(存储策略)进行上下文的存储。我们来看看SecurityContestHolderStrategy,到底是什么

SecurityContestHolderStrategy.java(全部源码

图片pic3

可知 SecurityContestHolderStrategy只是一个接口,这个接口提供创建、清空、获取、设置上下文的操作。那它有哪些实现类呢,也就是有哪些存储策略呢?

GlobalSecurityContextHolderStrategy.java(全部源码)

全局的上下文存取策略,只存储一个上下文,对应前面说的单机系统。

图片pic4

ThreadLocalSecurityContextHolderStrategy.java(全部源码)

图片pic5

基于ThreadLocal的存储策略实现,看上去,这个类好像跟上面那个全局的没什么差别。但是要注意了,它是用ThreadLocal来存储的。新手可能会疑惑,就一个变量,我怎么存储多个上下文,这个变量又不是集合。 这里就不分析源码了,实际上ThreadLocal内部会用数组来存储多个对象的。原理是,ThreadLocal会为每个线程开辟一个存储区域,来存储相应的对象。

Authentication——用户信息的表示: 在SecurityContextHolder中存储了当前与系统交互的用户的信息。Spring Security使用一个Authentication 对象来表示这些信息。一般不需要自己创建这个对象,但是查找这个对象的操作对用户来说却非常常见。

图片pic6

批注: ①Principal(准则)=> 允许通过的规则,即允许访问的规则,基本等价于UserDetails(用户信息)

源码分析: 我们来看看,这个SecurityContext(安全上下文),到底是个什么样子。 SecurityContext.java(全部源码

图片pic7

由源码可知,所谓的安全上下文,只是保存了Authentication(认证信息)。那认证信息包含哪些内容呢?

Authentication.java(全部源码)

图片pic8

由源码可知,Authentication(认证信息),主要包含了以下内容

  • 用户权限集合 => 可用于访问受保护资源时的权限验证
  • 用户证书(密码) => 初次认证的时候,进行填充,认证成功后将被清空
  • 细节 => 暂不清楚,猜测应该是记录哪些保护资源已经验证授权,下次不用再验证,等等。
  • Pirncipal => 大概就是账号吧
  • 是否已认证成功

参考链接

Read more

GrantedAuthority

默认实现SimpleGrantedAuthority

GrantedAuthority接口

我们知道UserDeitails接口里面有一个getAuthorities()方法。这个方法将返回此用户的所拥有的权限。这个集合将用于用户的访问控制,也就是Authorization。

所谓权限,就是一个字符串。一般不会重复。

所谓权限检查,就是查看用户权限列表中是否含有匹配的字符串。

package org.springframework.security.core;

import java.io.Serializable;

public interface GrantedAuthority extends Serializable {
    String getAuthority();
}

“角色”如何表示?与Shiro有何不同?

在Security中,角色和权限共用GrantedAuthority接口,唯一的不同角色就是多了个前缀”ROLE_“,而且它没有Shiro的那种从属关系,即一个角色包含哪些权限等等。在Security看来角色和权限时一样的,它认证的时候,把所有权限(角色、权限)都取出来,而不是分开验证。

所以,在Security提供的UserDetailsService默认实现JdbcDaoImpl中,角色和权限都存储在auhtorities表中。而不是像Shiro那样,角色有个roles表,权限有个permissions表。以及相关的管理表等等。

GrantedAuthority接口的默认实现SimpleGrantedAuthority

package org.springframework.security.core.authority;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert;

public final class SimpleGrantedAuthority implements GrantedAuthority {
    private static final long serialVersionUID = 500L;
    private final String role;

    public SimpleGrantedAuthority(String role) {
        Assert.hasText(role, "A granted authority textual representation is required");
        this.role = role;
    }

    public String getAuthority() {
        return this.role;
    }

    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        } else {
            return obj instanceof SimpleGrantedAuthority ? this.role.equals(((SimpleGrantedAuthority)obj).role) : false;
        }
    }

    public int hashCode() {
        return this.role.hashCode();
    }

    public String toString() {
        return this.role;
    }
}

注意,在构建SimpleGrantedAuthority对象的时候,它没有添加任何前缀。所以表示”角色”的权限,在数据库中就带有”ROLE_“前缀了。所以authorities表中的视图可能是这样的。

图片pic1

角色和权限能否分开存储?角色能不能不带”ROLE_“前缀

当然可以分开存储,你可以定义两张表,一张存角色,一张存权限。但是你自定义UserDetailsService的时候,需要保证把这两张表的数据都取出来,放到UserDails的权限集合中。当然你数据库中存储的角色也可以不带”ROLE_“前缀,就像这样。

图片pic2 图片pic3

但是前面说到了,Security才不管你是角色,还是权限。它只比对字符串。

比如它有个表达式hasRole(“ADMIN”)。那它实际上查询的是用户权限集合中是否存在字符串”ROLE_ADMIN”。如果你从角色表中取出用户所拥有的角色时不加上”ROLE_“前缀,那验证的时候就匹配不上了。

所以角色信息存储的时候可以没有"ROLE_"前缀,但是包装成GrantedAuthority对象的时候必须要有。

权限检查/访问控制方式

权限检查有两种方式,一种是在配置类中,指定粗粒度的访问控制,另一种是使用注解细粒度的控制访问。

粗粒度访问控制,所有URL以”/admin”开头的用户必须拥有角色”ADMIN”才能访问。实际上操作的时候hasRole表达式,会判断参数是否包含”ROLE_“前缀,如果没有则加上去,然后再去校验。有这个前缀则直接校验。

protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").access("hasRole('ADMIN')")
                .antMatchers("/user/**").access("hasRole('USER')")
                .anyRequest().authenticated();

}

细粒度的访问控制

注:需要使用注解@EnableGlobalMethodSecurity(prePostEnabled=true) 开启

@PreAuthoritze("hasAuthority('readArtical')")
public List<Artical> getAll() {
    //...
}

这个注解,会从SecurityContext中取出Authencation对象,然后再取出Collection authorites集合。然后比对当前用户是否有权限"readArtical"。实际上就是比对集合中是否有那个GrantedAuthority的getAuthority()方法返回的字符串与"radArtical"匹配。

参考链接

Read more

AuthenticationException 使用及所有子类

AuthenticationEntryPoint 用来解决匿名用户访问无权限资源时的异常

  1. AccountStatusException:用户状态异常它包含如下子类
    • AccountExpiredException:账户过期
    • CredentialsExpiredException:证书过期
    • DisabledException:账户不可用
    • LockedException:账户锁定
  2. ActiveDirectoryAuthenticationException,
  3. AuthenticationCancelledException:已取消OpenID身份验证
  4. AuthenticationCredentialsNotFoundException:如果由于SecurityContext中没有身份验证对象而拒绝身份验证请求,则抛出该异常。
  5. AuthenticationServiceException:如果由于系统问题而无法处理身份验证请求,则抛出该异常。例如,如果后端身份验证存储库不可用,则可能会抛出此错误。
  6. BadCredentialsException:如果由于凭据无效而拒绝身份验证请求,则抛出该异常。 对于抛出此异常,这意味着该帐户既未锁定也未禁用。
  7. InsufficientAuthenticationException:如果由于凭据未充分信任而拒绝身份验证请求,则抛出该异常。 如果AccessDecisionVoters对身份验证级别不满意,通常会引发此异常,例如使用“记住我”机制或匿名执行。 然后,ExceptionTranslationFilter通常将导致AuthenticationEntryPoint被调用,从而允许主体以更强的身份验证级别进行身份验证。
  8. NonceExpiredException:如果由于摘要随机数已过期而拒绝认证请求,则抛出该异常。
  9. OAuth2AuthenticationException:对于所有与OAuth 2.0相关的身份验证错误,都会引发此异常。在许多情况下可能会发生错误,例如:
    • 授权请求或令牌请求缺少必需的参数
    • 客户端标识符缺失或无效
    • 无效或不匹配的重定向URI
    • 请求的范围无效,未知或格式错误
    • 资源所有者或授权服务器拒绝了访问请求
    • 客户端身份验证失败
    • 提供的授权授权(授权代码,资源所有者凭证)无效,已过期或已撤销
  10. PreAuthenticatedCredentialsNotFoundException:
  11. ProviderNotFoundException:如果未找到支持所提供的Authentication对象的AuthenticationProvider,则由ProviderManager抛出。
  12. RememberMeAuthenticationException:使用“记住我”身份验证时发生身份验证异常时,将引发此异常。
  13. Saml2AuthenticationException:对于所有与SAML 2.0相关的身份验证错误,都会引发此异常。在许多情况下可能会发生错误,例如:
    • 响应或断言请求丢失或格式错误
    • 主题缺失或无效
    • 签名缺失或无效
    • 断言的时间段验证失败
    • 断言条件之一不满足
    • 解密失败
    • 无法找到主题标识符,通常称为用户名
  14. SessionAuthenticationException:由SessionAuthenticationStrategy抛出,以指示认证对象对于当前会话无效,通常是因为同一用户超过了允许其同时进行的会话数。
  15. UsernameNotFoundException:如果UserDetailsService实现无法通过用户名找到用户,则抛出该异常。
举例
@Slf4j
@Component
public class UnauthorizedHandler implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        // 用户登录时身份认证未通过
        if (e instanceof BadCredentialsException) {
            log.info("用户登录时身份认证失败!");
            ResultUtil.writeJavaScript(httpServletResponse, ErrorCodeEnum.TOKEN_INVALID.getCode(), e.getMessage());
        } else if (e instanceof InsufficientAuthenticationException) {
            log.info("缺少请求头参数,Authorization传递是token值所以参数是必须的.");
            ResultUtil.writeJavaScript(httpServletResponse, ErrorCodeEnum.NO_TOKEN.getCode(), ErrorCodeEnum.NO_TOKEN.getMessage());
        } else {
            log.info("用户token无效.");
            ResultUtil.writeJavaScript(httpServletResponse, ErrorCodeEnum.TOKEN_INVALID.getCode(), ErrorCodeEnum.TOKEN_INVALID.getMessage());
        }

    }
}
参考文档:

docs.spring.io

Read more

date工具

package sc.whorl.system.utils.date;

import org.apache.commons.lang3.time.DateFormatUtils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * date工具
 */
public class DateUtil {

    private static String[] parsePatterns = {
            "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
            "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
            "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};

    /**
     * 得到当前日期字符串 格式(yyyy-MM-dd)
     */
    public static String getDate() {
        return getDate("yyyy-MM-dd");
    }

    /**
     * 得到当前日期字符串 格式(yyyy-MM-dd) pattern可以为:"yyyy-MM-dd" "HH:mm:ss" "E"
     */
    public static String getDate(String pattern) {
        return DateFormatUtils.format(new Date(), pattern);
    }

    /**
     * 得到日期字符串 默认格式(yyyy-MM-dd) pattern可以为:"yyyy-MM-dd" "HH:mm:ss" "E"
     */
    public static String formatDate(Date date, Object... pattern) {
        String formatDate = null;
        if (pattern != null && pattern.length > 0) {
            formatDate = DateFormatUtils.format(date, pattern[0].toString());
        } else {
            formatDate = DateFormatUtils.format(date, "yyyy-MM-dd");
        }
        return formatDate;
    }

    /**
     * 得到日期时间字符串,转换格式(yyyy-MM-dd HH:mm:ss)
     */
    public static String formatDateTime(Date date) {
        return formatDate(date, "yyyy-MM-dd HH:mm:ss");
    }

    /**
     * 得到当前时间字符串 格式(HH:mm:ss)
     */
    public static String getTime() {
        return formatDate(new Date(), "HH:mm:ss");
    }

    /**
     * 得到当前日期和时间字符串 格式(yyyy-MM-dd HH:mm:ss)
     */
    public static String getDateTime() {
        return formatDate(new Date(), "yyyy-MM-dd HH:mm:ss");
    }

    /**
     * 得到当前年份字符串 格式(yyyy)
     */
    public static String getYear() {
        return formatDate(new Date(), "yyyy");
    }

    /**
     * 得到当前月份字符串 格式(MM)
     */
    public static String getMonth() {
        return formatDate(new Date(), "MM");
    }

    /**
     * 得到当天字符串 格式(dd)
     */
    public static String getDay() {
        return formatDate(new Date(), "dd");
    }

    /**
     * 得到当前星期字符串 格式(E)星期几
     */
    public static String getWeek() {
        return formatDate(new Date(), "E");
    }

    /**
     * 日期型字符串转化为日期 格式
     * { "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm",
     *   "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm",
     *   "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm" }
     */
    public static Date parseDate(Object str) {
        if (str == null){
            return null;
        }
        try {
            return new SimpleDateFormat(parsePatterns[0]).parse(str.toString());
        } catch (ParseException e) {
            return null;
        }
    }

    /**
     * 获取过去的天数
     * @param date
     * @return
     */
    public static long pastDays(Date date) {
        long t = System.currentTimeMillis()-date.getTime();
        return t/(24*60*60*1000);
    }

    /**
     * 获取过去的小时
     * @param date
     * @return
     */
    public static long pastHour(Date date) {

        long t = System.currentTimeMillis()-date.getTime();
        return t/(60*60*1000);
    }

    /**
     * 获取过去的分钟
     * @param date
     * @return
     */
    public static long pastMinutes(Date date) {
        long t = System.currentTimeMillis()-date.getTime();
        return t/(60*1000);
    }

    /**
     * 转换为时间(天,时:分:秒.毫秒)
     * @param timeMillis
     * @return
     */
    public static String formatDateTime(long timeMillis){
        long day = timeMillis/(24*60*60*1000);
        long hour = (timeMillis/(60*60*1000)-day*24);
        long min = ((timeMillis/(60*1000))-day*24*60-hour*60);
        long s = (timeMillis/1000-day*24*60*60-hour*60*60-min*60);
        long sss = (timeMillis-day*24*60*60*1000-hour*60*60*1000-min*60*1000-s*1000);
        return (day>0?day+",":"")+hour+":"+min+":"+s+"."+sss;
    }



}

Read more