springboot 加入token安全认证 手把手教程
Posted ‘喵’ 想吃鱼了
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot 加入token安全认证 手把手教程相关的知识,希望对你有一定的参考价值。
第一步:引入pom文件
<!--安全框架--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!--jwt加密--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.6.0</version> </dependency>
第二步:在启动类同路径下创建WebConfiguration 添加拦截器
@Configuration
public class WebConfiguration extends WebMvcConfigurationSupport
@Autowired
private TokenInterceptor tokenInterceptor;
@Override
//注册拦截器
protected void addInterceptors(InterceptorRegistry registry)
registry.addInterceptor(tokenInterceptor) //注册拦截器
.addPathPatterns("/**") //拦截一切请求(任何请求路径)
.excludePathPatterns("/**/login");//放过login登录请求
第三步:自定义拦截器
@Component
public class TokenInterceptor extends HandlerInterceptorAdapter
@Autowired
private JwtUtils jwtUtils;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
System.out.println("经过了拦截器");
//获得放置token的请求头
String token = request.getHeader("Authorization");
//校验头格式
if(StringUtils.isEmpty(token) || !token.startsWith("Bearer "))
throw new RuntimeException("请先登录");
//截取token字符串
token = token.substring(7);
//解析token
try
Claims claims = jwtUtils.parseToken(token);
request.setAttribute("claims",claims);
catch (Exception e)
throw new RuntimeException("请先登录");
return true;
第四步:引入jwt工具类
工具类要在启动类注入一下
@Bean public JwtUtils getJwtUtils() return new JwtUtils();
public class JwtUtils
//配置文件配置的
@Value("$jwt.ttl")
private long ttl; //指定token有效时间 (小时)
//密钥
@Value("$jwt.secret")
private String secret;
//生成Token
public String buildToken(String userId, String userName)
long currentTimeMillis = System.currentTimeMillis(); //当前系统时间
long expirationTimeMillis = currentTimeMillis + 1000*60*60*ttl; //过期时间
String token = Jwts.builder()
.signWith(SignatureAlgorithm.HS256, secret)//指定加密算法和密钥
.setId(userId) //添加载荷部分键值对 => 放置登录用户id
.setSubject(userName) //添加载荷部分键值对 => 放置登录用户名
.setIssuedAt(new Date()) //添加载荷部分键值对 => 放置签发token时间
.setExpiration(new Date(expirationTimeMillis))//指定token的过期时间
.compact();//生成密文token
return token;
//解析Token
public Claims parseToken(String token)
Claims claims = Jwts.parser() //返回token解析器
.setSigningKey(secret) //指定解析使用的密钥 => 需要跟加密时使用相同的密钥
.parseClaimsJws(token) // 指定要解密的Token
.getBody();//获得载荷部分
return claims;
第五步:请求类
@Autowired
private JwtUtils jwtUtils;
@RequestMapping(value = "/login",method = RequestMethod.POST)
public R login(//前端输入的账号和密码@RequestParam("userId") String userId,@RequestParam("passWord") String passWord)
//通过账号密码去数据库查数据 获取用户的信息
String token = jwtUtils.buildToken("用户id", "用户名称");
//R是封装的返回实体
token 这就拿到了,前端每次请求都需要携带着token,然后拦截器就会拦截请求校验token,成功才进请求,失败就返回错误信息。
踩坑需要注意的地方:前端每次请求会有一个预检options请求,要在拦截器过滤掉这个请求
if (HttpMethod.OPTIONS.toString().equals(request.getMethod()))
return true;
SpringBoot 整合 oauth2实现 token 认证
参考地址:https://www.jianshu.com/p/19059060036b
session和token的区别:
- session是空间换时间,而token是时间换空间。session占用空间,但是可以管理过期时间,token管理部了过期时间,但是不占用空间.
- sessionId失效问题和token内包含。
- session基于cookie,app请求并没有cookie 。
- token更加安全(每次请求都需要带上)
Oauth2 密码授权流程
在oauth2协议里,每一个应用都有自己的一个clientId和clientSecret(需要去认证方申请),所以一旦想通过认证,必须要有认证方下发的clientId和secret。
1. pom
<!--security--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> </dependency>
2. UserDetail实现认证第一步
MyUserDetailsService.java
@Autowired private PasswordEncoder passwordEncoder; /** * 根据进行登录 * @param username * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { log.info("登录用户名:"+username); String password = passwordEncoder.encode("123456"); //User三个参数 (用户名+密码+权限) //根据查找到的用户信息判断用户是否被冻结 log.info("数据库密码:"+password); return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); }
3. 获取token的控制器
1 @RestController 2 public class OauthController { 3 4 @Autowired 5 private ClientDetailsService clientDetailsService; 6 @Autowired 7 private AuthorizationServerTokenServices authorizationServerTokenServices; 8 @Autowired 9 private AuthenticationManager authenticationManager; 10 11 @PostMapping("/oauth/getToken") 12 public Object getToken(@RequestParam String username, @RequestParam String password, HttpServletRequest request) throws IOException { 13 Map<String,Object>map = new HashMap<>(8); 14 //进行验证 15 String header = request.getHeader("Authorization"); 16 if (header == null && !header.startsWith("Basic")) { 17 map.put("code",500); 18 map.put("message","请求投中无client信息"); 19 return map; 20 } 21 String[] tokens = this.extractAndDecodeHeader(header, request); 22 assert tokens.length == 2; 23 //获取clientId 和 clientSecret 24 String clientId = tokens[0]; 25 String clientSecret = tokens[1]; 26 //获取 ClientDetails 27 ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId); 28 if (clientDetails == null){ 29 map.put("code",500); 30 map.put("message","clientId 不存在"+clientId); 31 return map; 32 //判断 方言 是否一致 33 }else if (!StringUtils.equals(clientDetails.getClientSecret(),clientSecret)){ 34 map.put("code",500); 35 map.put("message","clientSecret 不匹配"+clientId); 36 return map; 37 } 38 //使用username、密码进行登录 39 UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, password); 40 //调用指定的UserDetailsService,进行用户名密码验证 41 Authentication authenticate = authenticationManager.authenticate(authentication); 42 HrUtils.setCurrentUser(authenticate); 43 //放到session中 44 //密码授权 模式, 组建 authentication 45 TokenRequest tokenRequest = new TokenRequest(new HashMap<>(),clientId,clientDetails.getScope(),"password"); 46 47 OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails); 48 OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request,authentication); 49 50 OAuth2AccessToken token = authorizationServerTokenServices.createAccessToken(oAuth2Authentication); 51 map.put("code",200); 52 map.put("token",token.getValue()); 53 map.put("refreshToken",token.getRefreshToken()); 54 return map; 55 } 56 57 /** 58 * 解码请求头 59 */ 60 private String[] extractAndDecodeHeader(String header, HttpServletRequest request) throws IOException { 61 byte[] base64Token = header.substring(6).getBytes("UTF-8"); 62 63 byte[] decoded; 64 try { 65 decoded = Base64.decode(base64Token); 66 } catch (IllegalArgumentException var7) { 67 throw new BadCredentialsException("Failed to decode basic authentication token"); 68 } 69 70 String token = new String(decoded, "UTF-8"); 71 int delim = token.indexOf(":"); 72 if (delim == -1) { 73 throw new BadCredentialsException("Invalid basic authentication token"); 74 } else { 75 return new String[]{token.substring(0, delim), token.substring(delim + 1)}; 76 } 77 } 78 }
4. 核心配置
(1)、Security 配置类 说明登录方式、登录页面、哪个url需要认证、注入登录失败/成功过滤器
1 @Configuration 2 public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter { 3 4 /** 5 * 注入 自定义的 登录成功处理类 6 */ 7 @Autowired 8 private MyAuthenticationSuccessHandler mySuccessHandler; 9 /** 10 * 注入 自定义的 登录失败处理类 11 */ 12 @Autowired 13 private MyAuthenticationFailHandler myFailHandler; 14 15 @Autowired 16 private ValidateCodeFilter validateCodeFilter; 17 18 /** 19 * 重写PasswordEncoder 接口中的方法,实例化加密策略 20 * @return 返回 BCrypt 加密策略 21 */ 22 @Bean 23 public PasswordEncoder passwordEncoder(){ 24 return new BCryptPasswordEncoder(); 25 } 26 27 @Override 28 protected void configure(HttpSecurity http) throws Exception { 29 //在UsernamePasswordAuthenticationFilter 过滤器前 加一个过滤器 来搞验证码 30 http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class) 31 //表单登录 方式 32 .formLogin() 33 .loginPage("/authentication/require") 34 //登录需要经过的url请求 35 .loginProcessingUrl("/authentication/form") 36 .passwordParameter("pwd") 37 .usernameParameter("user") 38 .successHandler(mySuccessHandler) 39 .failureHandler(myFailHandler) 40 .and() 41 //请求授权 42 .authorizeRequests() 43 //不需要权限认证的url 44 .antMatchers("/oauth/*","/authentication/*","/code/image").permitAll() 45 //任何请求 46 .anyRequest() 47 //需要身份认证 48 .authenticated() 49 .and() 50 //关闭跨站请求防护 51 .csrf().disable(); 52 //默认注销地址:/logout 53 http.logout(). 54 //注销之后 跳转的页面 55 logoutSuccessUrl("/authentication/require"); 56 } 57 58 /** 59 * 认证管理 60 * 61 * @return 认证管理对象 62 * @throws Exception 认证异常信息 63 */ 64 @Override 65 @Bean 66 public AuthenticationManager authenticationManagerBean() throws Exception { 67 return super.authenticationManagerBean(); 68 } 69 }
(2)、认证服务器
1 @Configuration 2 @EnableAuthorizationServer 3 public class MyAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { 4 @Autowired 5 private AuthenticationManager authenticationManager; 6 7 @Autowired 8 private MyUserDetailsService userDetailsService; 9 10 11 12 13 @Override 14 public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { 15 super.configure(security); 16 } 17 18 /** 19 * 客户端配置(给谁发令牌) 20 * @param clients 21 * @throws Exception 22 */ 23 @Override 24 public void configure(ClientDetailsServiceConfigurer clients) throws Exception { 25 clients.inMemory().withClient("internet_plus") 26 .secret("internet_plus") 27 //有效时间 2小时 28 .accessTokenValiditySeconds(72000) 29 //密码授权模式和刷新令牌 30 .authorizedGrantTypes("refresh_token","password") 31 .scopes( "all"); 32 } 33 34 @Override 35 public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { 36 endpoints 37 .authenticationManager(authenticationManager) 38 .userDetailsService(userDetailsService); 39 } 40 }
@EnableResourceServer
这个注解就决定了这是个资源服务器。它决定了哪些资源需要什么样的权限。
5、测试
以上是关于springboot 加入token安全认证 手把手教程的主要内容,如果未能解决你的问题,请参考以下文章