废话不多说,直接开始这个SpringSecurity
的学习项目。
数据库说明
用户信息表 USER
储存用户信息
代码语言:javascript复制DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'id',
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '名称',
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '密码',
`isEnable` bit(1) NULL DEFAULT NULL COMMENT '是否启用1、启用 0、禁用',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;
权限表PURVIEW
储存权限信息
代码语言:javascript复制DROP TABLE IF EXISTS `purview`;
CREATE TABLE `purview` (
`id` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '权限id',
`authority` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '权限名称',
`role` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '角色',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;
用户权限表PURVIEW
储存用户权限
代码语言:javascript复制DROP TABLE IF EXISTS `authority`;
CREATE TABLE `authority` (
`id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '用户权限id',
`authority` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '权限id',
`member_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '用户id',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;
基础的Security
安全配置
- 配置权限给
SpringSecurity
- 配置是否需要拦截的请求
- 配置请求处理
(success/error)
如何配置?
SecurityVerificationConfiguration
配置类也是最为核心的一个类,在其中配置了关于上面的一些信息
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityVerificationConfiguration extends WebSecurityConfigurerAdapter {
/**
* 密码加密
*
* @return
*/
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 拦截器
*/
@Autowired
public JwtAuthenticationFilter jwtAuthenticationFilter;
/**
* jwt 验证处理器
*/
@Autowired
public JwtAccessDeniedHandler jwtAccessDeniedHandler;
/**
* toekn 配置
*/
@Autowired
public TokenConfiguration tokenConfiguration;
@Autowired
PurviewService purviewService;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 授权 、 验证
*
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 添加权限
selectPurview(http);
http
.authorizeRequests()
// 授权地址不需要验证
.antMatchers("/auth/token").permitAll()
// 用户注册地址
.antMatchers("/user/registered").permitAll()
// 其余的都需要校验
.anyRequest().authenticated()
.and()
// 添加后置处理拦截器
.addFilterAfter(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling()
// 访问拒绝处理程序
.accessDeniedHandler(jwtAccessDeniedHandler)
.and()
.apply(tokenConfiguration)
.and()
// 取消 session 的状态
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable();
}
/**
* 查询权限并将权限放入 security 中
*
* @param http
* @throws Exception
*/
public void selectPurview(HttpSecurity http) throws Exception {
List<Purview> purviews = purviewService.selectList();
for (Purview purview : purviews) {
http.authorizeRequests()
.antMatchers(purview.getAuthority()).hasAnyAuthority(purview.getRole());
}
}
}
关于SecurityVerificationConfiguration
说明希望帮你解决一些疑惑
-
@EnableWebSecurity
注解开启web
安全验证 -
@EnableGlobalMethodSecurity(prePostEnabled=true)
启用基于注释的安全性 参考官方文档11.5 开启后可以通过@PreAuthorize
注解限制请求controller
的访问权限 -
selectPurview(HttpSecurity http)
方法查询所有的权限角色,并将权限角色交由SpringSecurity
管理 -
PasswordEncoder
密码加密 参考官方文档5.1.2 -
JwtAuthenticationFilter
是TOEKM
拦截器
看了基础的配置,按照 SpringSecurity 入门(二)
中提到的思路,需要构建 Authentication
认证对象,那么下面就是构建认证所需要的 Authentication
认证对象
如何构建?
在构建 Authentication
认证对象之前,还需要明确一个问题,构建Authentication
认证对象所需要的信息:
principal
显然这个使用final
修饰不可以修改,所以传递的值一定是在认证之后不需要修改的,例如:用户信息credentials
用于防止认证的信息,可以是TOKEN
authorities
权限集合
principal
很简单构建一个用户就可以了,像这样
public User(String username, String password, Collection<? extends GrantedAuthority> authorities)
credentials
就更简单了,把jwt
生成的 TOKEN
放进去就好了
authorities
权限集合,也不难,只需要查找用户拥有的权限就可以,这里有两种方式,一种是获取通过用户信息获取用户的权限,还有一种就是通过TOKEN
获取权限信息,但是第一种的限制比较而言更多,第二种也更为方便,所有我们通过解析TOKEN
来获取用户的权限
那首先应该如何构建这个TOKEN
是首要的
构建生成TOKEN
代码语言:javascript复制@Component
@Slf4j
public class JwtUtil {
/**
* 签名密钥
*/
@Value("${auth.token.signingKey}")
private String signingKey;
/**
* 创建生成 token
* <p>
* setClaims() 与 setSubject() 冲突所以不设置主体信息
*
* @param claim 用户权限 map
* @return String 生成的 token
*/
public String createToken(Map<String, Object> claim) {
return Jwts.builder()
// 设置唯一的 ida
.setId(IdUtil.simpleUUID())
// .claim("auth", "admin")
.setClaims(claim)
// 设置过期时间
.setExpiration(new DateUtil().getNowDateOneTime())
// 设置 token 签发的时间
.setIssuedAt(new DateTime())
// 设置签名 使用HS256算法,并设置SecretKey(字符串) 签名算法和秘钥
.signWith(SignatureAlgorithm.HS256, signingKey)
// 以下内容构建JWT并将其序列化为紧凑的,URL安全的字符串
.compact();
}
}
这里需要注意区分一下claim(String , Object)
和setClaims(Map<String, Object>)
claim
只能设置单个权限setClaims
可以设置多个权限对象
这里建议第二种,这样可以从TOKEN
中存放更多的有效信息
解析TOKEN
构建Authentication
认证对象
代码语言:javascript复制@Slf4j
@Component
public class TokenProvider {
// 权限密钥
private static final String AUTHORITIES_KEY = "auth";
// 用户信息
private static final String ID = "id";
// 签名密钥
@Value("${auth.token.signingKey}")
private String signingKey;
@Autowired
JwtUtil jwtUtil;
/**
* 获取 Spring Context 的 SecurityContext 对象
* 用于获取用户的身份验证
*
* @param token jwt 生成的 token 信息
* @return authentication 认证对象
*/
public Authentication getAuthentication(String token) {
// parser() 解析token
Claims claims = Jwts.parser()
.setSigningKey(signingKey)
.parseClaimsJws(token)
.getBody();
Object claim = claims.get(AUTHORITIES_KEY);
// 权限
String auth = "";
if (Objects.nonNull(claim)) {
auth = claim.toString();
}
// 权限集合
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(auth.split(","))
.filter(StringUtils::isNotBlank)
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
// 创建 Spring Security 的 user 对象
User principal = new User((String) claims.get(ID), "", authorities);
// 创建返回 Authentication 对象
return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}
}
需要先从TOKEN
中获取Claims
对象,并且获取权限信息,先构建 principal
用户信息,再通过用户信息构建一个Authentication
认证对象,到这里基本上就完成了80%
了,依照先前的思路完成了代码,但是有几个问题需要考虑
- 在什么时候构建
TOKEN
信息 当然是在用户登录的时候构建这样的一个安全认证信息的令牌,并且在访问时需要携带该令牌 - 在什么时候解析
TOKEN
信息,构建Authentication
认证对象 当然是在每一次访问接口的时候
权限、用户信息,现在都交给Spring Security
管理了,但是怎么实现在每一次访问接口的时候去构建这个Authentication
认证对象呢?
提醒一下,拦截器,OncePerRequestFilter
可以确保一次请求只会通过一次该过滤
过滤拦截请求
代码语言:javascript复制@Component
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {
/**
* 用户的业务逻辑层
*/
@Autowired
public UserService userService;
@Autowired
public JwtUtil jwtUtil;
@Autowired
private TokenProvider tokenProvider;
public JwtAuthenticationFilter(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
/**
* 与{@code doFilter}的合同相同,但保证在单个请求线程中每个请求仅被调用一次。
* 有关详细信息,请参见{@link #shouldNotFilterAsyncDispatch()}。
* <p>提供HttpServletRequest和HttpServletResponse参数,而不是默认的ServletRequest和ServletResponse参数。
*
* @param httpServletRequest
* @param httpServletResponse
* @param filterChain
* @throws IOException
* @throws ServletException
*/
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws IOException, ServletException {
if (!"/auth/token".equals(httpServletRequest.getRequestURI())) {
String token = httpServletRequest.getHeader(HttpHeaders.AUTHORIZATION);
if (token == null)
throw new TokenException(HttpStatus.HTTP_FORBIDDEN, "缺少验证信息");
Authentication authentication = tokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
需要排除一些接口,例如登录认证接口,其余都需要构建这样Authentication
认证对象
这就是按照我们思路实现的一套 认证,具体的操作表的代码就不展示了,按照思路往下去走就可以了。
Shao Jie :代码可能有些漏洞、BUG等等一些问题,逻辑也可能不够完美,只是提供一些思路,仅供参考,具体需要怎么做,自行参考官方文档,官方有更详细的解释,只是单纯的希望,能够给你帮助,代码问题可以在
GitHub
提ISSUES