1 SpringSecurity简介
SpringSecurity是一个基于Spring全家桶提供的安全访问控制解决方案的安全框架。它是一个功能强大、可高度定制的身份验证和访问控制框架。
JAVA开发者都知道,SpringSecurity是一个为Java程序提供的一个身份验证和授权的框架。
2 SpringBoot+Spring Security实现微信登录
注意一下,因为我们这里要做账号+微信网页授权登录,所以需要提前去微信开放平台准备好你的微信桓公需要的东西,这里就不一一演示了。
下面主要实现其核心代码逻辑。
2.1 Spring Security的WebSecurityConfig配置
package com.test.qyg.yzt.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
@EnableWebSecurity
public class SpringSecurityTestConfig extends WebSecurityConfigurerAdapter {
@Autowired
MyAuthenticationnSuccessHandler myAuthenticationSuccessHandler;
@Autowired MyAuthenticationFailureHandler myAuthenticationFailureHandler;
@Autowired
WxAuthenticationnSuccessHandler wxAuthenticationnSuccessHandler;
@Autowired
WxAuthenticationFailureHandler wxAuthenticationFailureHandler;
@Autowired private DataSource dataSource;
@Autowired
RedisOneNetUtil redisOneNetUtil;
@Value("${companyLog.loginPage}")
private String loginPage;
@Bean
public JdbcTokenRepositoryImpl tokenRepository() {
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);
return tokenRepository;
}
@Bean
UserService customUserService() { // 注册UserDetailsService 的bean
return new UserServiceImpl();
}
@Bean
UserService weChatUserService() { // 注册UserDetailsService 的bean
return new WeChatUserServiceImpl();
}
/**
* 此处给AuthenticationManager添加登陆验证的逻辑。
* 这里添加了两个AuthenticationProvider分别用于用户名密码登陆的验证以及token授权登陆两种方式。
* 在处理登陆信息的过滤器执行的时候会调用这两个provider进行登陆验证。
*/
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
//用户名和密码登陆
auth.userDetailsService(customUserService()).passwordEncoder(new BCryptPasswordEncoder());
//微信openid登陆
auth.authenticationProvider(weChatAuthenticationProvider());
}
/**
* 微信openid登陆处理
*/
@Bean
public WeChatAuthenticationProvider weChatAuthenticationProvider() {
return new WeChatAuthenticationProvider();
}
/**
* 添加微信openid登陆验证的过滤器
*/
@Bean
public WeChatAuthenticationFilter weChatAuthenticationFilter() throws Exception {
WeChatAuthenticationFilter filter = new WeChatAuthenticationFilter();
filter.setAuthenticationManager(authenticationManagerBean());
filter.setAuthenticationSuccessHandler(wxAuthenticationnSuccessHandler);
filter.setAuthenticationFailureHandler(wxAuthenticationFailureHandler);
return filter;
}
/**
* 添加用户名和密码登陆验证的过滤器
*/
@Bean
public CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
filter.setAuthenticationManager(authenticationManagerBean());
filter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler);
filter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);
return filter;
}
/**
* 配置请求拦截
*
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// http: // 192.168.1.225:8080/users/restPwdView?userid=6&taskcode=8grf3B
HttpMethodFilter filter = new HttpMethodFilter();
WeChatAuthenticationFilter wechatFilter = weChatAuthenticationFilter();
CustomAuthenticationFilter customFilter = customAuthenticationFilter();
ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
validateCodeFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);
// http.httpBasic() //httpBasic登录 BasicAuthenticationFilter
// 必须在注册之后的过滤器之间才能安插过滤器
http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(wechatFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(validateCodeFilter, HttpMethodFilter.class)
//表单登录,loginPage为登录请求的url,loginProcessingUrl为表单登录处理的URL
.formLogin()
.loginPage(loginPage)
// 登录需要经过的url请求
.loginProcessingUrl("/user/login")
.loginProcessingUrl("/wechat/weChatLogin")
//.successHandler(myAuthenticationSuccessHandler)
//.failureHandler(myAuthenticationFailureHandler)
.and()
.authorizeRequests()
.antMatchers(
loginPage,
"/comMonAssessScreens",
"/comMonAssessScreen",
"/alarmConfiguration/ifCheck",
"/logOut",
"/code/image",
"/meterData/insertElecMeterDataList",
"/meterDataCreate/*",
"/common/**",
"/common/js/**",
"/wechat/**",
"/weChatLogin",
"/weChatLogin.html",
"/indexV2")
.permitAll()
.antMatchers("/static/**")
.permitAll() // 不拦截静态资源
.antMatchers("/views/**")
.permitAll() // 不拦截静态资源
.antMatchers("/script/**")
.hasAuthority("ROLE_SuperPermission")
.antMatchers("/**")
.fullyAuthenticated()
// 需要身份认证
.and()
// 登出后根据用户读取登出页面
.logout()
.logoutUrl("/logOut") // 配置登出请求路径
.invalidateHttpSession(true)
.and()
.headers()
.frameOptions()
.sameOrigin()
.and()
.rememberMe()
.tokenRepository(tokenRepository())
.tokenValiditySeconds(3600) // Token过期时间为一个小时
.and()
.csrf()
.disable() // 注销行为任意访问
.headers()
// 增加csp防xss攻击 frame-ancestors 针对frame的加载策略 default-src 针对默认加载策略 object-src 针对插件的加载策略
.contentSecurityPolicy(
"frame-ancestors 'self'; default-src 'self' 'unsafe-inline' 'unsafe-eval' *.aliyuncs.com *.baidu.com *.bdimg.com ;object-src 'self'");
}
}
2.2 自定义生成token
本系统用账号密码token认证。
package com.test.qyg.yzt.config;
/**
* @description:用户名和密码验证的token
*/
public class UserAuthenticationToken extends UsernamePasswordAuthenticationToken {
/**
*
*/
private static final long serialVersionUID = -1076492615339314113L;
public UserAuthenticationToken(Object principal, Object credentials) {
super(principal, credentials);
}
public UserAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
super(principal, credentials, authorities);
}
}
微信登录token认证。
package com.test.qyg.yzt.config;
/**
* @description:微信验证的token`
*/
public class VXAuthenticationToken extends UsernamePasswordAuthenticationToken {
private static final long serialVersionUID = -6231962326068951783L;
public WeChatAuthenticationToken(Object principal) {
super(principal, "");
}
public WeChatAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(principal, "", authorities);
}
}
2.3 创建过滤器的实现
用户名和密码登录验证的过滤器。
package com.test.qyg.yzt.config;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @description:用户名和密码登陆验证的过滤器
*/
public class CustomAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
private boolean postOnly = true;
public CustomAuthenticationFilter() {
//父类中定义了拦截的请求URL,/login的post请求,直接使用这个配置,也可以自己重写
super("/user/login");
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
CustomAuthenticationToken authRequest = new CustomAuthenticationToken(username, password);
// Allow subclasses to set the "details" property
setDetails(request,authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
protected String obtainPassword(HttpServletRequest request) {
String password =request.getParameter(passwordParameter);
return password == null ? "" : password;
}
/**
* Enables subclasses to override the composition of the username, such as by
* including additional values and a separator.
*
* @param request so that request attributes can be retrieved
*
* @return the username that will be presented in the <code>Authentication</code>
* request token to the <code>AuthenticationManager</code>
*/
protected String obtainUsername(HttpServletRequest request) {
String username =request.getParameter(usernameParameter);
return username == null ? "" : username;
}
protected void setDetails(HttpServletRequest request,
UsernamePasswordAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
}
微信登录验证的过滤器。
package com.test.qyg.yzt.config;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @description:微信openid登陆验证的过滤器
*/
public class WeChatAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private String openidParameter = "openid";
public WeChatAuthenticationFilter() {
super("/wechat/weChatLogin");
//super.setAuthenticationFailureHandler(new MyAuthenticationFailureHandler());
}
/**
* {@inheritDoc}
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
if (!request.getMethod().equals(HttpMethod.GET.name())) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String openid = obtainOpenid(request);
if (openid == null || openid.length() == 0) {
throw new BadCredentialsException("uid or openid is null.");
}
WeChatAuthenticationToken authRequest = new WeChatAuthenticationToken(openid);
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
return this.getAuthenticationManager().authenticate(authRequest);
}
protected String obtainOpenid(HttpServletRequest request) {
String openid = request.getParameter(this.openidParameter);
return openid == null ? "" : openid.trim();
}
}
2.4 创建Provider实现微信登录
package com.sllt.qyg.yzt.config;
import com.mysql.cj.protocol.AuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @description:
*/
public class VXAuthenticationProvider implements AuthenticationProvider {
@Autowired
UserWeChatClient userWeChatClient;
@Autowired
UserControllerClient userControllerClient;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication.isAuthenticated()) {
return authentication;
}
//获取过滤器封装的token信息
WeChatAuthenticationToken authenticationToken = (WeChatAuthenticationToken) authentication;
String openid = (String)authenticationToken.getPrincipal();
//这里逻辑省略,根据自己的逻辑需要修改
//拿到微信的openid后,去数据库更具openid查询已经管理网站的用户,查到用户信息就直接返回用户信息和权限信息即可
WeChatAuthenticationToken authenticationResult = new WeChatAuthenticationToken(openid, authorities);
return authenticationResult;
}
@Override
public boolean supports(Class<?> authentication) {
return WeChatAuthenticationToken.class.isAssignableFrom(authentication);
}
}
2.5 创建Handler验证微信是否登录成功
如果微信登录成功后跳转到网站首页。
import org.springframework.stereotype.Service;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
*
* 微信登陆成功后跳转到首页
*/
@Service
public class VXAuthenticationnSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
response.sendRedirect("/index.html");
}
}
微信登录验证失败返回错误信息。
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Service;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
*
* 微信登录验证失败返回错误信息
*/
@Service
public class WxAuthenticationFailureHandler implements AuthenticationFailureHandler {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
// 返回json数据
Map result = new HashMap();
result.put("wx_success", false);
result.put("codeRtn", false);
// 错误信息
result.put("errorMsg", exception.getMessage());
String json = objectMapper.writeValueAsString(result);
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(json);
}
}
2.6 创建Service实现用户逻辑
如果是用户名和密码登录方式就走该逻辑。
import org.springframework.stereotype.Service;
/**
* 定义UserDetailsService 接口
*/
@Service
public class UserServiceImpl implements UserDetailsService {
// 授权过程
@Override
/** 根据数据库获得用户信息,并且查询出权限,返回带有权限的用户信息。 */
public User loadUserByUsername(String username) {
//这里代码逻辑省略,按照izji实际业务逻辑编写去数据库拿用户信息即可
return new User(user.getUsername(), user.getPassword(), grantedAuthorities);
} else {
throw new UsernameNotFoundException("admin: " + username + " do not exist!");
}
}
}