1. 首页
  2. >
  3. 编程技术
  4. >
  5. Java

ruoyi前后端分离框架增加只通过用户名认证获取token

背景:

最近选型使用ruoyi的前后端分离框架进行研发,除了管理系统部分还有小程序部分功能,需要提供api给小程序部分,而小程序使用openid关联用户名,这样希望提供只根据用户名能够获取token进行后续后台服务接口的访问需求,具体改造如下:

增加5个关键类和一个测试类 修改一个config类,具体如下:

拦截特殊验证的拦截器类

package com.ruoyi.framework.config.q;  import java.io.IOException;  import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;  import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.util.StringUtils;  public class QAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter  { 	     public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "key";     private String keyParameter = SPRING_SECURITY_FORM_USERNAME_KEY;          public QAuthenticationProcessingFilter() {         super(new AntPathRequestMatcher("/loginq", "GET"));     }      @Override     public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {          String key = obtainKey(request);         if (StringUtils.isEmpty(key)) {             throw new AuthenticationServiceException("key不能为空");         }          // TODO 根据key 查询username         String username = key;                           return this.getAuthenticationManager().authenticate(new QAuthenticationToken(username));     }          protected String obtainKey(HttpServletRequest request) {         return request.getParameter(keyParameter);     } } 

验证实际处理类

package com.ruoyi.framework.config.q;  import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService;  public class QAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { 	     /**      * 自定义用户认证逻辑      */     @Autowired     private UserDetailsService userDetailsService;          @Override     protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {      }      @Override     public Authentication authenticate(Authentication authentication) throws AuthenticationException {         QAuthenticationToken qToken = (QAuthenticationToken) authentication;         try {             UserDetails userDetails = userDetailsService.loadUserByUsername(qToken.getUserName());             return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());         } catch (Exception e) {             logger.error(e);             throw new BadCredentialsException("q登录异常:" + e.getMessage());         }      }      @Override     protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {         return null;     }      @Override     public boolean supports(Class<?> authentication) {         return (QAuthenticationToken.class.isAssignableFrom(authentication));     }   } 

验证通过后的处理类

package com.ruoyi.framework.config.q;  import java.io.IOException; import java.io.PrintWriter;  import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;  import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;  import com.alibaba.fastjson.JSONObject; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.framework.web.service.TokenService;  public class QAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {      @Autowired     private TokenService tokenService;          @Override     public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {         //super.onAuthenticationSuccess(request, response, authentication);                  AjaxResult ajax = AjaxResult.success();                  String token = tokenService.createToken(SecurityUtils.getLoginUser());         ajax.put(Constants.TOKEN, token);                  response.setContentType("application/json;charset=utf-8");         response.setStatus(HttpStatus.OK.value());         PrintWriter out = response.getWriter();         out.write(JSONObject.toJSONString(ajax));         out.flush();         out.close();     }  } 
package com.ruoyi.framework.config.q;  import java.io.IOException; import java.io.PrintWriter;  import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;  import org.springframework.http.HttpStatus; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler;  import com.alibaba.fastjson.JSONObject; import com.ruoyi.common.core.domain.AjaxResult;  public class QAuthenticationFailureHandler implements AuthenticationFailureHandler { 	@Override     public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {          		AjaxResult ajax = AjaxResult.error(exception.getMessage()); 		 		response.setContentType("application/json;charset=utf-8");         response.setStatus(HttpStatus.OK.value());         PrintWriter out = response.getWriter();         out.write(JSONObject.toJSONString(ajax));         out.flush();         out.close();     } }

验证bean对象

package com.ruoyi.framework.config.q;  import org.springframework.security.authentication.AbstractAuthenticationToken;  public class QAuthenticationToken extends AbstractAuthenticationToken {       private static final long serialVersionUID = 1L; 	private String userName;      public QAuthenticationToken(String userName) {         super(null);         this.setUserName(userName);     }      @Override     public Object getCredentials() {         return null;     }      @Override     public Object getPrincipal() {         return null;     }  	public String getUserName() { 		return userName; 	}  	public void setUserName(String userName) { 		this.userName = userName; 	}  } 

测试类如下

package com.ruoyi.framework.config.q;  import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;  import com.ruoyi.common.core.domain.AjaxResult;  @RestController public class QTestBizController {  	/** 	 * 登录方法 	 *  	 * @param loginBody 登录信息 	 * @return 结果 	 */ 	@GetMapping("/loginq/test") 	public AjaxResult test() { 		AjaxResult ajax = AjaxResult.success(); 		ajax.put("test", "test123"); 		return ajax; 	}  } 

6.SecurityConfig配置中增加部分配置,详细差别请自行对比

package com.ruoyi.framework.config;  import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; 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.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.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.web.filter.CorsFilter;  import com.ruoyi.framework.config.q.QAuthenticationFailureHandler; import com.ruoyi.framework.config.q.QAuthenticationProcessingFilter; import com.ruoyi.framework.config.q.QAuthenticationProvider; import com.ruoyi.framework.config.q.QAuthenticationSuccessHandler; import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter; import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl; import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl;  /**  * spring security配置  *   * @author ruoyi  */ @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter {     /**      * 自定义用户认证逻辑      */     @Autowired     private UserDetailsService userDetailsService;          /**      * 认证失败处理类      */     @Autowired     private AuthenticationEntryPointImpl unauthorizedHandler;      /**      * 退出处理类      */     @Autowired     private LogoutSuccessHandlerImpl logoutSuccessHandler;      /**      * token认证过滤器      */     @Autowired     private JwtAuthenticationTokenFilter authenticationTokenFilter;      /**      * 跨域过滤器      */     @Autowired     private CorsFilter corsFilter;          /**      * 解决 无法直接注入 AuthenticationManager      *      * @return      * @throws Exception      */     @Bean     @Override     public AuthenticationManager authenticationManagerBean() throws Exception     {         return super.authenticationManagerBean();     }      /**      * anyRequest          |   匹配所有请求路径      * access              |   SpringEl表达式结果为true时可以访问      * anonymous           |   匿名可以访问      * denyAll             |   用户不能访问      * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)      * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问      * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问      * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问      * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问      * hasRole             |   如果有参数,参数表示角色,则其角色可以访问      * permitAll           |   用户可以任意访问      * rememberMe          |   允许通过remember-me登录的用户访问      * authenticated       |   用户登录后可访问      */     @Override     protected void configure(HttpSecurity httpSecurity) throws Exception     {         httpSecurity                 // CSRF禁用,因为不使用session                 .csrf().disable()                 // 认证失败处理类                 .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()                 // 基于token,所以不需要session                 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()                 // 过滤请求                 .authorizeRequests()                 // 对于登录login 验证码captchaImage 允许匿名访问                 .antMatchers("/login", "/captchaImage").anonymous()                 .antMatchers(                         HttpMethod.GET,                         "/*.html",                         "/**/*.html",                         "/**/*.css",                         "/**/*.js"                 ).permitAll()                 .antMatchers("/profile/**").anonymous()                 .antMatchers("/common/download**").anonymous()                 .antMatchers("/common/download/resource**").anonymous()                 .antMatchers("/swagger-ui.html").anonymous()                 .antMatchers("/swagger-resources/**").anonymous()                 .antMatchers("/webjars/**").anonymous()                 .antMatchers("/*/api-docs").anonymous()                 .antMatchers("/druid/**").anonymous()                 // 除上面外的所有请求全部需要鉴权认证                 .anyRequest().authenticated()                 .and()                 .headers().frameOptions().disable();         httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);         // 添加JWT filter         httpSecurity.addFilterBefore(qAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);         httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);         // 添加CORS filter         httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);         httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);     }          @Bean     public QAuthenticationProcessingFilter qAuthenticationProcessingFilter() throws Exception {     	QAuthenticationProcessingFilter filter = new QAuthenticationProcessingFilter();     	filter.setAuthenticationSuccessHandler(qAuthenticationSuccessHandler());     	filter.setAuthenticationFailureHandler(qAuthenticationFailureHandler());         filter.setAuthenticationManager(super.authenticationManagerBean());         return filter;     }          @Bean     public QAuthenticationSuccessHandler qAuthenticationSuccessHandler() {     	// 不配置跳转地址,为了返回数据流     	QAuthenticationSuccessHandler qAuthenticationSuccessHandler = new QAuthenticationSuccessHandler();         return qAuthenticationSuccessHandler;     }          @Bean     public QAuthenticationFailureHandler qAuthenticationFailureHandler() {     	// 不配置跳转地址,为了返回数据流     	QAuthenticationFailureHandler qAuthenticationFailureHandler = new QAuthenticationFailureHandler();         return qAuthenticationFailureHandler;     }          /**      * 强散列哈希加密实现      */     @Bean     public BCryptPasswordEncoder bCryptPasswordEncoder()     {         return new BCryptPasswordEncoder();     }       @Bean     public QAuthenticationProvider qAuthenticationProvider() {         return new QAuthenticationProvider();     }          /**      * 身份认证接口      */     @Override     protected void configure(AuthenticationManagerBuilder auth) throws Exception     {     	auth.authenticationProvider(qAuthenticationProvider());         auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());     } } 

测试效果方式如下:

通过访问
http://localhost:8088/loginq?key=admin获取token

正确结果是:

{"msg":"操作成功","code":200,"token":"
eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImFjNTcwMmJhLWQyYTItNDBlZS1hYjVkLTI2ZDExYjU4Yzk5OSJ9.R2ROekesiKL1fngPletnl6WfSqka-GGKq0zkBrHfnjIyAyjGtaENOM6L_AJKI5c4XXEBr0iOl44chY4RqJbwlw"}

如果未获取到结果如下:

{"msg":"key不能为空","code":500}


通过访问
http://localhost:8088/loginq/test并且在header头中增加Authorization为key,值为上一个返回的token执行测试