Spring Security是基于JavaEE标准中的Filter实现的。
Spring Security的源码是Spring全家桶中最复杂的之一。推荐学习网站www.spring4all.com
Spring Security包括认证与授权部分。
使用方法
导入依赖包后Spring Security直接接管了整个系统的认证,自带了登录页面和自动生成了一个账号密码。
需要修改三个地方配置Spring Security:
- User对象实现UserDetails接口;
- UserService实现UserDetailsService接口;
- 新建一个配置类(带@Configuration注解)继承WebSecurityConfigurerAdapter类,重写三个参数不同的configure方法。
1.User对象实现UserDetails接口
主要是实现getAuthorities方法返回User的权限标识。
关于User权限标识的手段,复杂的有用户表+角色表+权限表;简单的使用加个type成员标记即可。
public class User implements UserDetails {
private int id;
private String username;
private String password;
private String salt;
private String email;
private int type;
private int status;
private String activationCode;
private String headerUrl;
private Date createTime;
//省略Getter和Setter
@Override
public boolean isAccountNonExpired() {
return true;// true: 账号未过期.
}
@Override
public boolean isAccountNonLocked() {
return true;// true: 账号未锁定
}
@Override
public boolean isCredentialsNonExpired() {
return true;//true:凭证未过期
}
@Override
public boolean isEnabled() {
return true;//true:账号可用
}
//获取权限标识
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> list = new ArrayList<>();
list.add((GrantedAuthority) () -> {
switch (type){
case 1:return "ADMIN";
case 2:return "MODERATOR";
default:return "USER";
}
});
return list;
}
}
2.UserService实现UserDetailsService接口
实现loadUserByUsername方法即可,非常简单。
@Service
public class UserService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
public User findUserByName(String username) {
return userMapper.selectByName(username);
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return this.findUserByName(username);
}
}
3.新建一个配置类继承WebSecurityConfigurerAdapter类,重写三个configure方法
这里涉及较多Spring Security的组件概念,比较容易搞乱。
三个configure方法:
- configure(WebSecurity web)用于配置不过滤静态资源访问;
- configure(AuthenticationManagerBuilder auth)实现自定义的认证逻辑;
- configure(HttpSecurity http)定义http请求的认证行为,定义授权,remember-me和增加自定义Filter。
/**
* 配置Spring Security,这里涉及较多组件不要搞乱
*
* @author cuiwj
* @date 2020/3/31
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
@Override
public void configure(WebSecurity web) throws Exception {
// 不过滤静态资源的访问
web.ignoring().antMatchers("/resources/**");
}
// AuthenticationManager: 认证的核心接口.
// AuthenticationManagerBuilder: 用于构建AuthenticationManager对象的工具.
// ProviderManager: AuthenticationManager接口的默认实现类.
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 内置的认证规则
// auth.userDetailsService(userService).passwordEncoder(new Pbkdf2PasswordEncoder("12ab"));
//本系统已使用md5(password1+salt)的自定义认证规则
// AuthenticationProvider: ProviderManager持有一组AuthenticationProvider,每个AuthenticationProvider负责一种认证.
// 委托模式: ProviderManager将认证委托给AuthenticationProvider.
auth.authenticationProvider(new AuthenticationProvider() {
// 这个方法具体实现自定义的认证规则
// Authentication: 用于封装认证信息的接口,不同的实现类代表不同类型的认证信息.
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = (String) authentication.getCredentials();
User user = userService.findUserByName(username);
if (user == null) {
throw new UsernameNotFoundException("账号不存在!");
}
password = CommunityUtil.md5(password + user.getSalt());
if (!user.getPassword().equalsIgnoreCase(password)) {
throw new BadCredentialsException("密码不正确!");
}
// principal: 主要信息; credentials: 证书; authorities: 权限;
return new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
}
// 当前的AuthenticationProvider支持哪种类型的认证.
@Override
public boolean supports(Class<?> aClass) {
// UsernamePasswordAuthenticationToken: Authentication接口的常用的实现类.
return UsernamePasswordAuthenticationToken.class.equals(aClass);
}
});
}
@Override
protected void configure(HttpSecurity http) throws Exception {
/* 登录相关配置 */
http.formLogin()
.loginPage("/loginpage")
.loginProcessingUrl("/login")
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//认证成功的行为:重定向到主页
response.sendRedirect(request.getContextPath() + "/index");
}
})
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
//认证成功的行为:request带上错误提示,转发到登录页面
request.setAttribute("error", e.getMessage());
request.getRequestDispatcher("/loginpage").forward(request, response);
}
});
/*退出相关配置*/
http.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//退出成功的行为:重定向到首页
response.sendRedirect(request.getContextPath() + "/index");
}
});
/*request路径授权配置*/
http.authorizeRequests()
.antMatchers("/letter").hasAnyAuthority("ADMIN", "USER")
.antMatchers("/admin").hasAnyAuthority("ADMIN")
.and().exceptionHandling().accessDeniedPage("/denied");
/*记住我相关配置*/
http.rememberMe()
.userDetailsService(userService)
.tokenRepository(new InMemoryTokenRepositoryImpl())//内置的token持久化类,存在内存里;可自定义实现一个
.tokenValiditySeconds(3600 * 24);
/*增加自定义Filter实现额外功能*/
//自定义Filter处理验证码
http.addFilterBefore(new Filter() {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
if (request.getServletPath().equals("/login")) {
String verifyCode = request.getParameter("verifyCode");
if (verifyCode == null || !verifyCode.equalsIgnoreCase("1234")) {//为方便验证码已写死
request.setAttribute("error", "验证码错误!");
request.getRequestDispatcher("/loginpage").forward(request, response);
return;
}
}
chain.doFilter(request, response);
}
}, UsernamePasswordAuthenticationFilter.class);
}
}
Spring Security认证成功后,认证结果会通过SecurityContextHolder存入SecurityContext中。在程序的其他地方可以通过SecurityContextHolder类获取User对象。如:
@RequestMapping(path = "/index", method = RequestMethod.GET)
public String getIndexPage(Model model) {
// 认证成功后,结果会通过SecurityContextHolder存入SecurityContext中.
Object obj = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (obj instanceof User) {
model.addAttribute("loginUser", obj);
}
return "/index";
}
代码链接:https://github.com/Treyoo/springsecuritydemo