赞
踩
SpringSecurity是一个功能强大且高度可定制的身份验证和访问控制框架,提供了完善的认证机制和方法级的授权功能,是一款非常优秀的权限管理框架,他的核心是一组过滤链,不同的功能经由不同的过滤器
Json Web Token(JWT) 是为了在网络应用环境间传递声明而执行的一种基于json的开放标准(RFC 7519),token设计为紧凑且安全,特别适用于分布式站点的单点登录(SSO)场景
jwt实际是一个字符串,由头部,载荷,签名组成,用[.]分隔,在服务器直接根据token取出保存的用户信息,即可对token的可用性进行校验,使得单点登录更简单
users表:
- DROP TABLE IF EXISTS `users`;
- CREATE TABLE `users` (
- `user_id` int NOT NULL AUTO_INCREMENT,
- `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
- `user_pwd` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
- PRIMARY KEY (`user_id`) USING BTREE
- ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;
role表:
- DROP TABLE IF EXISTS `role`;
- CREATE TABLE `role` (
- `id` int NOT NULL AUTO_INCREMENT,
- `user_id` int NULL DEFAULT NULL,
- `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
- PRIMARY KEY (`id`) USING BTREE
- ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;
- package cn.zb.security.service.impl;
-
- import cn.zb.security.entity.Role;
- import cn.zb.security.entity.Users;
- import cn.zb.security.service.RoleService;
- import cn.zb.security.service.UsersService;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.security.core.authority.SimpleGrantedAuthority;
- import org.springframework.security.core.userdetails.User;
- 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 java.util.ArrayList;
- import java.util.List;
-
- @Service
- public class UserDetailsServiceImpl implements UserDetailsService {
- @Autowired
- private UsersService usersService;
-
- @Autowired
- private RoleService roleService;
-
- /**
- * 用户登录
- * @param s 获取到的UserName,
- * @return user 返回的是org.springframework.security.core.userdetails.User;
- * @throws UsernameNotFoundException
- */
- @Override
- public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
- if (s==null || "".equals("s")){
- throw new RuntimeException("用户不能为空");
- }
- //根据用户名查询对象
- Users user = usersService.findUserByUserName(s);
- if (user==null){
- throw new RuntimeException("用户不存在");
- }
- List<SimpleGrantedAuthority> authorities=new ArrayList<>();
- //根据userId获取Role对象
- List<Role> roles = roleService.findRoleByUserId(user.getUserId());
- for (Role role:roles) {
- authorities.add(new SimpleGrantedAuthority("ROLE_"+role.getName()));
- }
- //这里没有对用户密码进行判断,将数据库中查到的密码封装到了对象中,在WebSecurityConfig中configure()方法中,User进行了验证,
- //前端传递的密码是明文,数据中存的是暗文,需要对前端传递的密码加密,进行验证,加密方法是new BCryptPasswordEncoder().encode("pwd")
- return new User(user.getUserName(),user.getUserPwd(),authorities);
- }
- }
4.创建JwtUtils工具类,做token的生成和校验
- package cn.zb.security.util;
-
- import io.jsonwebtoken.Claims;
- import io.jsonwebtoken.Jwts;
- import io.jsonwebtoken.SignatureAlgorithm;
-
- import java.util.Date;
- import java.util.HashMap;
-
- public class JwtUtils {
- public static final String TOKEN_HEADER="Authorization"; //token请求头
- public static final String TOKEN_PREFIX="Bearer";//token前缀
- public static final long EXPIRATION=60*60*1000; //token有效期
- public static final String SUBJECT="piconjo"; //签名主题
- public static final String HEADER_STRING="Passport"; //配置token在http heads中的键值
- public static final String APPSECRET_KEY="piconjo_secret"; //应用密钥
- public static final String ROLE_CLAIMS="role"; //角色权限声明
-
- //生成token
- public static String createToken(String username,String role){
- HashMap<String, Object> map = new HashMap<>();
- map.put(ROLE_CLAIMS,role);
- String token=Jwts.builder()
- .setSubject(username)
- .setClaims(map)
- .claim("username",username)
- .setIssuedAt(new Date())
- .setExpiration(new Date(System.currentTimeMillis()+EXPIRATION))
- .signWith(SignatureAlgorithm.HS512,APPSECRET_KEY)
- .compact();
- return token;
- }
-
- //校验token
- public static Claims checkJWT(String token){
- try{
- Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
- return claims;
- }catch (Exception e){
- e.printStackTrace();
- return null;
- }
- }
-
- //从Token中获取username
- public static String getUsername(String token){
- Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
- return claims.get("username").toString();
- }
-
- //从Token中获取用户角色
- public static String getUserRole(String token){
- Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
- return claims.get("role").toString();
- }
-
- //校验Token是否过期
- public static boolean isExpiration(String token){
- Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
- return claims.getExpiration().before(new Date());
- }
- }
springsercurity对UserDetailsServiceImpl返回的user进行验证,验证成功则生成token,失败则返回失败信息
- package cn.zb.security.util;
- import com.alibaba.fastjson.JSON;
- import org.springframework.security.authentication.*;
- import org.springframework.security.core.Authentication;
- import org.springframework.security.core.AuthenticationException;
- import org.springframework.security.core.GrantedAuthority;
- import org.springframework.security.core.userdetails.User;
- import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
-
- import javax.servlet.FilterChain;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
- import java.util.Collection;
-
-
- public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
- private AuthenticationManager authenticationManager;
-
- public JWTAuthenticationFilter (AuthenticationManager authenticationManager) {
- this.authenticationManager = authenticationManager;
- //默认的登录路径是/login,post请求
- super.setFilterProcessesUrl("/api/login");
- }
-
- //验证操作, 接受并解析用户凭证
- @Override
- public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
- //从输入流中获取到登录的信息
- //创建一个token并条用authenticationManager.authenticate 让Spring Security进行验证
- return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
- request.getParameter("username"),request.getParameter("password")));
- }
-
- /**
- * 验证【成功】后调用的方法
- * 若验证成功 生成token并返回
- */
- @Override
- protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException {
- User user= (User) authResult.getPrincipal();
- System.out.println(user.toString());
- // 从User中获取权限信息
- Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
- // 创建Token
- String token = JwtUtils.createToken(user.getUsername(), authorities.toString());
-
- // 设置编码 防止乱码问题
- response.setCharacterEncoding("UTF-8");
- response.setContentType("application/json; charset=utf-8");
- // 在请求头里返回创建成功的token
- // 设置请求头为带有"Bearer "前缀的token字符串
- response.setHeader("token", JwtUtils.TOKEN_PREFIX + token);
- // 处理编码方式 防止中文乱码
- response.setContentType("text/json;charset=utf-8");
- // 将反馈塞到HttpServletResponse中返回给前台
- response.getWriter().write(JSON.toJSONString("登录成功"));
- }
- /**
- * 验证【失败】调用的方法
- */
- @Override
- protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
- String returnData="";
- // 账号过期
- if (failed instanceof AccountExpiredException) {
- returnData="账号过期";
- }
- // 密码错误
- else if (failed instanceof BadCredentialsException) {
- returnData="密码错误";
- }
- // 密码过期
- else if (failed instanceof CredentialsExpiredException) {
- returnData="密码过期";
- }
- // 账号不可用
- else if (failed instanceof DisabledException) {
- returnData="账号不可用";
- }
- //账号锁定
- else if (failed instanceof LockedException) {
- returnData="账号锁定";
- }
- // 用户不存在
- else if (failed instanceof InternalAuthenticationServiceException) {
- returnData="用户不存在";
- }
- // 其他错误
- else{
- returnData="未知异常";
- }
-
- // 处理编码方式 防止中文乱码
- response.setContentType("text/json;charset=utf-8");
- // 将反馈塞到HttpServletResponse中返回给前台
- response.getWriter().write(JSON.toJSONString(returnData));
- }
- }
对前端传递的请求拦截,取出里面的token做token校验,获取UsernamePasswordAuthenticationToken对象返回springsecurity,进行相关的角色判断
- package cn.zb.security.util;
-
- import org.apache.commons.lang3.StringUtils;
- import org.springframework.security.authentication.AuthenticationManager;
- import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
- import org.springframework.security.core.Authentication;
- import org.springframework.security.core.AuthenticationException;
- import org.springframework.security.core.authority.SimpleGrantedAuthority;
- import org.springframework.security.core.context.SecurityContextHolder;
- import org.springframework.security.web.AuthenticationEntryPoint;
- import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
-
- import javax.servlet.FilterChain;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.List;
-
- public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
-
- private AuthenticationManager authenticationManager;
-
- public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
- super(authenticationManager);
- }
-
- @Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
-
- String tokenHeader=request.getHeader(JwtUtils.TOKEN_HEADER);
- //因为设置拦截全部请求,所有这里没有token放行之后是会返回没有登录
- if (tokenHeader==null || !tokenHeader.startsWith(JwtUtils.TOKEN_PREFIX)){
- chain.doFilter(request,response);
- return;
- }
- //若请求头中由token,则调用下面的方法进行解析,并设置认证信息
- SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
- super.doFilterInternal(request,response,chain);
- }
-
- public UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader){
- //去掉前缀,获取token字符串
- String token = tokenHeader.replace(JwtUtils.TOKEN_PREFIX, "");
- //从token中解密获取用户用户名
- String username=JwtUtils.getUsername(token);
- //从token中解密获取用户角色
- String role=JwtUtils.getUserRole(token);
- String[] roles = StringUtils.strip(role, "[]").split(",");
- List<SimpleGrantedAuthority> authorities=new ArrayList<>();
- for (String s : roles) {
- authorities.add(new SimpleGrantedAuthority(s));
- }
- if (username!=null){
- //这里像SpringSecurity声明用户角色,做相关操作放行
- return new UsernamePasswordAuthenticationToken(username,null,authorities);
- }
- return null;
- }
- }
7.创建WebSecurityConfig配置类,对springsecurity进行相关配置
- package cn.zb.security.config;
-
-
- import cn.zb.security.util.JWTAuthenticationEntryPoint;
- import cn.zb.security.util.JWTAuthenticationFilter;
- import cn.zb.security.util.JWTAuthorizationFilter;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Qualifier;
- import org.springframework.context.annotation.Bean;
- import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
- import org.springframework.security.config.annotation.web.builders.HttpSecurity;
- import org.springframework.security.config.annotation.web.builders.WebSecurity;
- 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.core.userdetails.UserDetailsService;
- import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
- import org.springframework.security.crypto.password.PasswordEncoder;
- import org.springframework.web.cors.CorsConfiguration;
- import org.springframework.web.cors.CorsConfigurationSource;
- import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
-
- @EnableWebSecurity
- public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
-
- @Autowired
- @Qualifier("userDetailsServiceImpl")
- private UserDetailsService userDetailsService;
-
- @Override
- protected void configure(AuthenticationManagerBuilder auth) throws Exception {
- auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
- }
-
- /**
- * 安全配置
- */
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http.cors()
- .and()
- //跨域伪造请求限制无效
- .csrf().disable()
- .authorizeRequests()
- //访问/data路径下的请求需要admin
- .antMatchers("/data/**").hasRole("admin")
- //拦截所有请求
- .antMatchers("/").authenticated()
- //对登录做放行,生成token
- .antMatchers("/api/login").permitAll()
- .and()
- //添加jwt登录拦截器
- .addFilter(new JWTAuthenticationFilter(authenticationManager()))
- //添加jwt鉴权拦截器
- .addFilter(new JWTAuthorizationFilter(authenticationManager()))
- .sessionManagement()
- //关闭session
- .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
- .and()
- //异常处理
- .exceptionHandling()
- .authenticationEntryPoint(new JWTAuthenticationEntryPoint());
-
- }
-
- //使用BCryptPasswordEncoder密码加密
- @Bean
- public PasswordEncoder passwordEncoder() {
- return new BCryptPasswordEncoder();
- }
-
- //跨域配置
- @Bean
- CorsConfigurationSource corsConfigurationSource(){
- UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
- //注册跨域配置
- source.registerCorsConfiguration("/**",new CorsConfiguration().applyPermitDefaultValues());
- return source;
- }
-
- //静态资源放行
- @Override
- public void configure(WebSecurity web) {
- // 设置拦截忽略文件夹,可以对静态资源放行
- //web.ignoring().antMatchers("/images/**");
- }
- }
我们在WebSecurityConfig 配置过,想要访问/data下的方法需要用户拥有admin角色
- package cn.zb.security.controller;
-
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
-
- @RestController
- @RequestMapping("/data")
- public class UserController {
- @GetMapping("/test")
- private String data() {
- return "访问成功";
- }
- }
用admin和as两个用户,其中as是没有admin权限的
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。