首先在sprintboot项目中引入SpringSecurity 依赖,如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
sprintboot Security 核心配置类WebSecurityConfigurerAdapter,继承该类 根据需求选择重写它的三个重载方法configure:
1、 重写configure(WebSecurity web)方法配置可以直接访问的资源:
@Override
public void configure(WebSecurity web) throws Exception {
// 给不需要权限能访问的资源
web.ignoring().antMatchers("/index.html", "/static/**", "/favicon.ico");
}
2、重写configure(AuthenticationManagerBuilder auth)配置判断依据:
a: 在代码中写死帐号和密码并使用官方提供的加密方式
// BCryptPasswordEncoder为官方提供的加密类
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(passwordEncoder)
//设置admin用户的密码为‘123’,权限为‘admin'
.withUser("admin").password(passwordEncoder.encode("123")).roles("ADMIN")
.and()
//设置test用户的密码为‘t123’,权限为‘user'
.withUser("test").password(passwordEncoder.encode("t123")).roles("USER");
}
b: 从数据库读取用户和密码采用默认的方式校验和自定义加密方式
建立UserBean.java,需要实现UserDetails接口
package com.security.sprintsecurity.bean;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import lombok.Data;
@Data
public class User implements UserDetails{
/**
*
*/
private static final long serialVersionUID = 1L;
private String username;
private String password;
//账户权限集
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// TODO Auto-generated method stub
//我们返回的权限必须带有“ROLE_”开头才可以,spring会自己截取ROLE_后边的字符串
//如下为用户的两个权限
String role1="ROLE_role1";
String role2="ROLE_role1";
GrantedAuthority authority1=new SimpleGrantedAuthority(role1);
GrantedAuthority authority2=new SimpleGrantedAuthority(role2);
List<GrantedAuthority> list=new ArrayList<>();
list.add(authority1);
list.add(authority2);
return list;
}
//账户是否未过期
@Override
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return true;
}
//账户是否未锁定
@Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return true;
}
//凭证是否未过期
@Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return true;
}
//账户片是否可用
@Override
public boolean isEnabled() {
// TODO Auto-generated method stub
return true;
}
}
建立UserService.java,需要实现UserDetailsService接口
package com.security.sprintsecurity.service;
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 com.security.sprintsecurity.bean.UserBean;
@Service
public class UserService implements UserDetailsService {
//在loadUserByUsername查询数据库
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// TODO Auto-generated method stub
UserBean user = new UserBean();
//此处可以从数据库读数据
user.setUsername("ll");
user.setPassword("123");
return user;
}
}
配置从数据库中读用户和密码以及自定义加密方式
@Autowired
UserService userService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//配置从数据库中读数据
auth.userDetailsService(userService)
//自定义加密方式需要实现PasswordEncoder接口
.passwordEncoder(new PasswordEncoder() {
//加密方式
@Override
public String encode(CharSequence rawPassword) {
// TODO Auto-generated method stub
return (String) rawPassword;
}
//密码比对
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
// TODO Auto-generated method stub
//encodedPassword为数据库中保存的加密密码
//rawPassword为用户输入的密码
return encode(rawPassword).equals(encodedPassword);
}
});
}
c: 自定义验证方式,实现AuthenticationProvider接口
@Autowired
UserService userService;
//添加校验规则
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// TODO Auto-generated method stub
String userName = (String) authentication.getPrincipal(); //拿到username
String password = (String) authentication.getCredentials(); //拿到password
UserDetails userDetails = userService.selectUserByUsername(userName);//自定义得到用户信息的方法
if (userDetails != null) {
// 构建返回的用户登录成功的token
return new UsernamePasswordAuthenticationToken(userDetails, password,userDetails.getAuthorities());
}
/*验证不通过*/
return null;
}
3、重写configure(HttpSecurity http)方法配置登陆处理
a: 接受表单格式数据(a=1&b=1),即通过request.getParameter(”“)获取前端数据
@Override
protected void configure(HttpSecurity http) throws Exception {
//接受表单格式数据(a=1&b=1),即通过request.getParameter(”“)获取
http
//.anonymous().disable()//禁止匿名,
.csrf().disable() //关闭csrf
.formLogin()//设置为表单登陆,如果不设置则会使用默认登陆页面
.loginPage("/login.html")//登陆页面路径
.loginProcessingUrl("/login")//登陆提交数据路径
.usernameParameter("username").passwordParameter("password")//设置表单中对应的用户名和密码参数名。默认为username和password
// .defaultSuccessUrl("")//认证成功后的默认跳转页面
// .failureUrl("")//登录失败转发的url,重定向后响应的Location中带有(“?error”)
// .failureForwardUrl("")//登录失败转发的url,重定向后响应的Location中不带有(“?error”)
// .successForwardUrl("")//登录成功转发的url
.successHandler(new AuthenticationSuccessHandler() {//自定义对成功的处理
private RequestCache requestCache = new HttpSessionRequestCache();
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// TODO Auto-generated method stub
//返回数据
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("成功");
//重定向
// SavedRequest savedRequest = requestCache.getRequest(request, response);
// if (savedRequest == null){
// redirectStrategy.sendRedirect(request, response, "/index");
// }else {
// redirectStrategy.sendRedirect(request, response, savedRequest.getRedirectUrl());
// }
}
})
.failureHandler(new AuthenticationFailureHandler() {//自定义对失败的处理
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
// TODO Auto-generated method stub
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(exception.getMessage());
}
})
.permitAll()//表单登录,permitAll()表示不需要验证登录页面,登录失败页面
.and()
.logout()//退出登陆
.logoutUrl("/logout")//退出登陆路径
// .logoutSuccessUrl("/my/index")//登录退出转发的url
.logoutSuccessHandler(new LogoutSuccessHandler() {//退出登陆自定义处理返回
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// TODO Auto-generated method stub
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(authentication.getName());
}
})
.permitAll()
.and()
.authorizeRequests().anyRequest().authenticated()//表示其他的都需要验证
.and()
.sessionManagement()//session管理
.maximumSessions(1)//最大session 数量(一个用户的最大同时登陆数)
.maxSessionsPreventsLogin(true)//超过最大sessin数量后时候阻止登录
// .expiredSessionStrategy(null)//自定义session过期错略
// .sessionRegistry(null)//自定义的session 注册表
.expiredUrl("/");//会话失效后跳转的url
}
b: 接收json格式数据
需要继承UsernamePasswordAuthenticationFilter 类,并重写attemptAuthentication方法
package com.security.sprintsecurity;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)
|| request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
ObjectMapper mapper = new ObjectMapper();
UsernamePasswordAuthenticationToken authRequest = null;
try (InputStream is = request.getInputStream()) {
Map<String,String> authenticationBean = mapper.readValue(is, Map.class);
authRequest = new UsernamePasswordAuthenticationToken(
authenticationBean.get("username"), authenticationBean.get("password"));
} catch (IOException e) {
e.printStackTrace();
authRequest = new UsernamePasswordAuthenticationToken(
"", "");
} finally {
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
else {
return super.attemptAuthentication(request, response);
}
}
}
重写configure(HttpSecurity http)方法
CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
//登陆成功处理
filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write("成功");
out.flush();
out.close();
}
});
//登陆失败处理
filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write("失败");
out.flush();
out.close();
}
});
//登陆失败处理
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()//设置为表单登陆,如果不设置则会使用默认登陆页面
.loginPage("/login.html")//登陆页面路径
.loginProcessingUrl("/login")//登陆提交数据路径
.permitAll()//表单登录,permitAll()表示这个不需要验证 登录页面,登录失败页面
.and().csrf().disable()
.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.logout()//退出登陆
.logoutUrl("/logout")//退出登陆路径
// .logoutSuccessUrl("/my/index")//登录退出转发的url
.logoutSuccessHandler(new LogoutSuccessHandler() {//退出登陆自定义处理返回
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// TODO Auto-generated method stub
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(authentication.getName());
}
})
.permitAll()
.and()
.authorizeRequests().anyRequest().authenticated();//表示其他的都需要验证
}
4、 权限管理
a: 在代码中写死权限
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()//设置为表单登陆,如果不设置则会使用默认登陆页面
.loginPage("/login.html")//登陆页面路径
.loginProcessingUrl("/login")//登陆提交数据路径
.permitAll()//表单登录,permitAll()表示这个不需要验证 登录页面,登录失败页面
.and().csrf().disable()
.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.logout()//退出登陆
.logoutUrl("/logout")//退出登陆路径
// .logoutSuccessUrl("/my/index")//登录退出转发的url
.logoutSuccessHandler(new LogoutSuccessHandler() {//退出登陆自定义处理
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// TODO Auto-generated method stub
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(authentication.getName());
}
})
.permitAll()
//权限管理
.and()
.authorizeRequests()
.antMatchers("/index").permitAll()//这就表示 /index这个页面不需要权限认证,所有人都可以访问
.antMatchers("/admin").hasRole("admin") //这就表示/admin的这个资源需要有ROLE_admin的这个角色才能访问。不然就会提示拒绝访问
.antMatchers(HttpMethod.POST,"/test/*").hasRole("u")//这就表示/test/路径下post请求的资源需要有ROLE_user的这个角色才能访问。不然就会提示拒绝访问
.anyRequest().authenticated()//其他请求必须经过认证以后才能访问
.and()
.exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {//权限不足自定义处理
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
// TODO Auto-generated method stub
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("application/json;charset=UTF-8");
PrintWriter out = response.getWriter();
out.write("权限错误");
out.flush();
out.close();
}
});
}
b: 基于RBAC自定义全限控制
@Component("rbacService")
public class RbacServiceImpl {
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
Object principal = authentication.getPrincipal();
Collection<GrantedAuthority> urs = (Collection<GrantedAuthority>) authentication.getAuthorities();
if (principal instanceof UserDetails) { //首先判断先当前用户是否是我们UserDetails对象。
List<String> rules = new ArrayList();
rules.add("ROLE_admin"); // 数据库读取该url所需要的权限
for (String rule : rules) {
for(GrantedAuthority ur: urs) {
if(String.valueOf(ur).equals(rule)) {
return true;
}
}
}
}
return false;
}
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()//设置为表单登陆,如果不设置则会使用默认登陆页面
.loginPage("/login.html")//登陆页面路径
.loginProcessingUrl("/login")//登陆提交数据路径
.permitAll()//表单登录,permitAll()表示这个不需要验证 登录页面,登录失败页面
.and().csrf().disable()
.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.logout()//退出登陆
.logoutUrl("/logout")//退出登陆路径
// .logoutSuccessUrl("/my/index")//登录退出转发的url
.logoutSuccessHandler(new LogoutSuccessHandler() {//退出登陆自定义处理
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// TODO Auto-generated method stub
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(authentication.getName());
}
})
.permitAll()
//权限管理
.and()
.authorizeRequests()
.anyRequest().access("@rbacService.hasPermission(request,authentication)")//必须经过认证以后才能访问
.and()
.exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {//权限不足自定义处理
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
// TODO Auto-generated method stub
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("application/json;charset=UTF-8");
PrintWriter out = response.getWriter();
out.write("权限错误");
out.flush();
out.close();
}
});
}
c: 自定义权限过滤器和自定义决策管理器实现权限控制,需要实现FilterInvocationSecurityMetadataSource和AccessDecisionManager类。
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()//设置为表单登陆,如果不设置则会使用默认登陆页面
.loginPage("/login.html")//登陆页面路径
.loginProcessingUrl("/login")//登陆提交数据路径
.permitAll()//表单登录,permitAll()表示这个不需要验证 登录页面,登录失败页面
.and().csrf().disable()
.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.logout()//退出登陆
.logoutUrl("/logout")//退出登陆路径
// .logoutSuccessUrl("/my/index")//登录退出转发的url
.logoutSuccessHandler(new LogoutSuccessHandler() {//退出登陆自定义处理
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// TODO Auto-generated method stub
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(authentication.getName());
}
})
.permitAll()
//权限管理
.and()
.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
// TODO Auto-generated method stub
//返回本次访问url需要的权限,可以有多个权限
object.setSecurityMetadataSource(new FilterInvocationSecurityMetadataSource() {
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
// TODO Auto-generated method stub
// String requestUrl = ((FilterInvocation) object).getRequestUrl();
String[] rules = {"ROLE_admin"};
return SecurityConfig.createList(rules);
}
//此处方法如果做了实现,返回了定义的权限资源列表,
//Spring Security会在启动时校验每个ConfigAttribute是否配置正确,
//如果不需要校验,这里实现方法,方法体直接返回null即可。
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
// TODO Auto-generated method stub
return null;
}
//方法返回类对象是否支持校验,
//web项目一般使用FilterInvocation来判断,或者直接返回true
@Override
public boolean supports(Class<?> clazz) {
// TODO Auto-generated method stub
return FilterInvocation.class.isAssignableFrom(clazz);
}
});
object.setAccessDecisionManager(new AccessDecisionManager() {
@Override
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
// TODO Auto-generated method stub
if (authentication == null) {
throw new AccessDeniedException("当前访问没有权限");
}
Iterator<ConfigAttribute> iterator = configAttributes.iterator();
while (iterator.hasNext()) {
ConfigAttribute ca = iterator.next();
//当前请求需要的权限
String needRole = ca.getAttribute();
//当前用户所具有的权限
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
if (authority.getAuthority().equals(needRole)) {
return;
}
}
}
throw new AccessDeniedException("当前访问没有权限");
}
@Override
public boolean supports(ConfigAttribute attribute) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean supports(Class<?> clazz) {
// TODO Auto-generated method stub
return false;
}
});
return object;
}
})
.and()
.exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {//权限不足自定义处理
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
// TODO Auto-generated method stub
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("application/json;charset=UTF-8");
PrintWriter out = response.getWriter();
out.write("权限错误");
out.flush();
out.close();
}
});
}
5、记住我功能
@Autowired
private DruidDataSource dataSource;
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);
tokenRepository.setCreateTableOnStartup(true); //系统在启动的时候生成“记住我”的数据表(只能使用一次)
return tokenRepository;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//接受表单格式数据(a=1&b=1),即通过request.getParameter(”“)获取
http
// .anonymous().disable()//禁止匿名,
.csrf().disable() //关闭csrf
.formLogin()//设置为表单登陆,如果不设置则会使用默认登陆页面
.loginPage("/login.html")//登陆页面路径
.loginProcessingUrl("/login")//登陆提交数据路径
// .usernameParameter("username").passwordParameter("password")//设置表单中对应的用户名和密码参数名。默认为username和password
// .defaultSuccessUrl("")//认证成功后的默认跳转页面
// .failureUrl("")//登录失败转发的url,重定向后响应的Location中带有(“?error”)
// .failureForwardUrl("")//登录失败转发的url,重定向后响应的Location中不带有(“?error”)
// .successForwardUrl("")//登录成功转发的url
.permitAll()//表单登录,permitAll()表示这个不需要验证 登录页面,登录失败页面
.successHandler(new AuthenticationSuccessHandler() {//自定义对成功的处理
private RequestCache requestCache = new HttpSessionRequestCache();
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// TODO Auto-generated method stub
//返回数据
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("成功");
//重定向
// SavedRequest savedRequest = requestCache.getRequest(request, response);
// if (savedRequest == null){
// redirectStrategy.sendRedirect(request, response, "/index");
// }else {
// redirectStrategy.sendRedirect(request, response, savedRequest.getRedirectUrl());
// }
}
})
.failureHandler(new AuthenticationFailureHandler() {//自定义对失败的处理
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
// TODO Auto-generated method stub
response.setContentType("application/json;charset=utf-8");
String msg = "";
if (exception instanceof BadCredentialsException ||
exception instanceof UsernameNotFoundException) {
msg = "账户名或者密码输入错误!";
} else if (exception instanceof LockedException) {
msg = "账户被锁定,请联系管理员!";
} else if (exception instanceof CredentialsExpiredException) {
msg = "密码过期,请联系管理员!";
} else if (exception instanceof AccountExpiredException) {
msg = "账户过期,请联系管理员!";
} else if (exception instanceof DisabledException) {
msg = "账户被禁用,请联系管理员!";
} else {
msg = "登录失败!";
}
response.setStatus(401);
response.getWriter().write(msg);
}
})
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(new LogoutSuccessHandler() {//退出登陆自定义处理
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// TODO Auto-generated method stub
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(authentication.getName());
}
})
.and()
.authorizeRequests().anyRequest().authenticated()//表示其他的都需要验证
.and()
.sessionManagement()//session管理
.maximumSessions(1)//最大session 数量(一个用户的最大同时登陆数)
.maxSessionsPreventsLogin(true)//超过最大sessin数量后时候阻止登录
// .expiredSessionStrategy(null)//自定义session过期错略
// .sessionRegistry(null)//自定义的session 注册表
.expiredUrl("/login.html")//会话失效后跳转的url
.and()
.rememberMe()
// .rememberMeParameter("remember-me")//记住我功能在表单中的key
.userDetailsService(userService)//使用userDetailsService用Token从数据库中获取用户自动登录
.tokenRepository(persistentTokenRepository())//持久化token
.tokenValiditySeconds(60);//设置Token的有效时间
}
WebSecurityConfigurer配置类的所有代码
package com.security.sprintsecurity.config;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.ObjectPostProcessor;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.alibaba.druid.pool.DruidDataSource;
import com.security.sprintsecurity.service.UserService;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Autowired
UserService userService;
@Override
public void configure(WebSecurity web) throws Exception {
// 给不需要权限能访问的资源
web.ignoring().antMatchers("/index.html", "/js/*", "/image/*", "/css/*", "/fonts/*", "/favicon.ico");
}
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//配置从数据库中读数据
auth.authenticationProvider(new AuthenticationProvider() {
//自定义验证方式,实现AuthenticationProvider接口
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// TODO Auto-generated method stub
String userName = (String) authentication.getPrincipal(); //拿到username
String password = (String) authentication.getCredentials(); //拿到password
UserDetails userDetails = userService.selectUserByUsername(userName);//自定义得到用户信息的方法
//添加校验规则
if (userDetails != null) {
// 构建返回的用户登录成功的token
return new UsernamePasswordAuthenticationToken(userDetails, password,userDetails.getAuthorities());
}
/*验证不通过*/
return null;
}
@Override
public boolean supports(Class<?> authentication) {
// TODO Auto-generated method stub
//如果该AuthenticationProvider支持传入的Authentication对象,则返回true
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
});
// //从数据库读取用户和密码采用默认的方式校验和自定义加密方式
// auth.userDetailsService(userService)
// //自定义加密方式需要实现PasswordEncoder接口
// .passwordEncoder(new PasswordEncoder() {
// //加密方式
// @Override
// public String encode(CharSequence rawPassword) {
// // TODO Auto-generated method stub
// return (String) rawPassword;
// }
//
// //密码比对
// @Override
// public boolean matches(CharSequence rawPassword, String encodedPassword) {
// // TODO Auto-generated method stub
// //encodedPassword为数据库中保存的加密密码
// //rawPassword为用户输入的密码
// return encode(rawPassword).equals(encodedPassword);
// }
//
// });
// //在代码中写死帐号和密码并使用官方提供的加密方式
// auth.inMemoryAuthentication().passwordEncoder(passwordEncoder)
// .withUser("admin").password(passwordEncoder.encode("123")).roles("ADMIN")
// .and()
// .withUser("test").password(passwordEncoder.encode("t123")).roles("USER");
}
CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
//登陆成功处理
filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write("成功");
out.flush();
out.close();
}
});
//登陆失败处理
filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write("失败");
out.flush();
out.close();
}
});
//登陆失败处理
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)
|| request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
ObjectMapper mapper = new ObjectMapper();
UsernamePasswordAuthenticationToken authRequest = null;
try (InputStream is = request.getInputStream()) {
Map<String,String> authenticationBean = mapper.readValue(is, Map.class);
authRequest = new UsernamePasswordAuthenticationToken(
authenticationBean.get("username"), authenticationBean.get("password"));
} catch (IOException e) {
e.printStackTrace();
authRequest = new UsernamePasswordAuthenticationToken(
"", "");
} finally {
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
else {
return super.attemptAuthentication(request, response);
}
}
}
@Component("rbacService")
public class RbacServiceImpl {
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
Object principal = authentication.getPrincipal();
Collection<GrantedAuthority> urs = (Collection<GrantedAuthority>) authentication.getAuthorities();
if (principal instanceof UserDetails) { //首先判断先当前用户是否是我们UserDetails对象。
List<String> rules = new ArrayList();
rules.add("ROLE_admin"); // 数据库读取该url所需要的权限
for (String rule : rules) {
for(GrantedAuthority ur: urs) {
if(String.valueOf(ur).equals(rule)) {
return true;
}
}
}
}
return false;
}
}
@Autowired
private DruidDataSource dataSource;
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);
tokenRepository.setCreateTableOnStartup(true); //系统在启动的时候生成“记住我”的数据表(只能使用一次)
return tokenRepository;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//接受表单格式数据(a=1&b=1),即通过request.getParameter(”“)获取
http
// .anonymous().disable()//禁止匿名,
.csrf().disable() //关闭csrf
.formLogin()//设置为表单登陆,如果不设置则会使用默认登陆页面
.loginPage("/login.html")//登陆页面路径
.loginProcessingUrl("/login")//登陆提交数据路径
// .usernameParameter("username").passwordParameter("password")//设置表单中对应的用户名和密码参数名。默认为username和password
// .defaultSuccessUrl("")//认证成功后的默认跳转页面
// .failureUrl("")//登录失败转发的url,重定向后响应的Location中带有(“?error”)
// .failureForwardUrl("")//登录失败转发的url,重定向后响应的Location中不带有(“?error”)
// .successForwardUrl("")//登录成功转发的url
.permitAll()//表单登录,permitAll()表示这个不需要验证 登录页面,登录失败页面
.successHandler(new AuthenticationSuccessHandler() {//自定义对成功的处理
private RequestCache requestCache = new HttpSessionRequestCache();
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// TODO Auto-generated method stub
//返回数据
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("成功");
//重定向
// SavedRequest savedRequest = requestCache.getRequest(request, response);
// if (savedRequest == null){
// redirectStrategy.sendRedirect(request, response, "/index");
// }else {
// redirectStrategy.sendRedirect(request, response, savedRequest.getRedirectUrl());
// }
}
})
.failureHandler(new AuthenticationFailureHandler() {//自定义对失败的处理
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
// TODO Auto-generated method stub
response.setContentType("application/json;charset=utf-8");
String msg = "";
if (exception instanceof BadCredentialsException ||
exception instanceof UsernameNotFoundException) {
msg = "账户名或者密码输入错误!";
} else if (exception instanceof LockedException) {
msg = "账户被锁定,请联系管理员!";
} else if (exception instanceof CredentialsExpiredException) {
msg = "密码过期,请联系管理员!";
} else if (exception instanceof AccountExpiredException) {
msg = "账户过期,请联系管理员!";
} else if (exception instanceof DisabledException) {
msg = "账户被禁用,请联系管理员!";
} else {
msg = "登录失败!";
}
response.setStatus(401);
response.getWriter().write(msg);
}
})
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(new LogoutSuccessHandler() {//退出登陆自定义处理
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// TODO Auto-generated method stub
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(authentication.getName());
}
})
.and()
.rememberMe()
// .rememberMeParameter("remember-me")//记住我功能在表单中的key
.userDetailsService(userService)//使用userDetailsService用Token从数据库中获取用户自动登录
.tokenRepository(persistentTokenRepository())//持久化token
.tokenValiditySeconds(60)//设置Token的有效时间
.and()
.authorizeRequests().anyRequest().authenticated()//表示其他的都需要验证
.and()
.sessionManagement()//session管理
.maximumSessions(1)//最大session 数量(一个用户的最大同时登陆数)
.maxSessionsPreventsLogin(true)//超过最大sessin数量后时候阻止登录
// .expiredSessionStrategy(null)//自定义session过期错略
// .sessionRegistry(null)//自定义的session 注册表
.expiredUrl("/login.html");//会话失效后跳转的url
//获取json或者表单数据
// http
// .formLogin()//设置为表单登陆,如果不设置则会使用默认登陆页面
// .loginPage("/login.html")//登陆页面路径
// .loginProcessingUrl("/login")//登陆提交数据路径
// .permitAll()//表单登录,permitAll()表示这个不需要验证 登录页面,登录失败页面
// .and().csrf().disable()
// .addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
// .logout()//退出登陆
// .logoutUrl("/logout")//退出登陆路径
// // .logoutSuccessUrl("/my/index")//登录退出转发的url
// .logoutSuccessHandler(new LogoutSuccessHandler() {//退出登陆自定义处理
// @Override
// public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
// Authentication authentication) throws IOException, ServletException {
// // TODO Auto-generated method stub
// response.setContentType("application/json;charset=utf-8");
// response.getWriter().write(authentication.getName());
// }
// })
// .permitAll()
// //权限管理
// .and()
// .authorizeRequests()
.antMatchers("/index").permitAll()//这就表示 /index这个页面不需要权限认证,所有人都可以访问
.antMatchers("/admin").hasRole("admin") //这就表示/admin的这个资源需要有ROLE_admin的这个角色才能访问。不然就会提示拒绝访问
.antMatchers(HttpMethod.POST,"/test/*").hasRole("u")//这就表示/test/路径下post请求的资源需要有ROLE_user的这个角色才能访问。不然就会提示拒绝访问
.anyRequest().authenticated()//必须经过认证以后才能访问
.anyRequest().access("@rbacService.hasPermission(request,authentication)")//必须经过认证以后才能访问
// .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
//
// @Override
// public <O extends FilterSecurityInterceptor> O postProcess(O object) {
// // TODO Auto-generated method stub
// //返回本次访问url需要的权限,可以有多个权限
// object.setSecurityMetadataSource(new FilterInvocationSecurityMetadataSource() {
//
// @Override
// public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
// // TODO Auto-generated method stub
String requestUrl = ((FilterInvocation) object).getRequestUrl();
// String[] rules = {"ROLE_admin"};
// return SecurityConfig.createList(rules);
// }
//
//
// //此处方法如果做了实现,返回了定义的权限资源列表,
// //Spring Security会在启动时校验每个ConfigAttribute是否配置正确,
// //如果不需要校验,这里实现方法,方法体直接返回null即可。
// @Override
// public Collection<ConfigAttribute> getAllConfigAttributes() {
// // TODO Auto-generated method stub
// return null;
// }
//
// //方法返回类对象是否支持校验,
// //web项目一般使用FilterInvocation来判断,或者直接返回true
// @Override
// public boolean supports(Class<?> clazz) {
// // TODO Auto-generated method stub
// return FilterInvocation.class.isAssignableFrom(clazz);
// }
//
// });
// object.setAccessDecisionManager(new AccessDecisionManager() {
//
// @Override
// public void decide(Authentication authentication, Object object,
// Collection<ConfigAttribute> configAttributes)
// throws AccessDeniedException, InsufficientAuthenticationException {
// // TODO Auto-generated method stub
// if (authentication == null) {
// throw new AccessDeniedException("当前访问没有权限");
// }
// Iterator<ConfigAttribute> iterator = configAttributes.iterator();
// while (iterator.hasNext()) {
// ConfigAttribute ca = iterator.next();
// //当前请求需要的权限
// String needRole = ca.getAttribute();
// //当前用户所具有的权限
// Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
// for (GrantedAuthority authority : authorities) {
// if (authority.getAuthority().equals(needRole)) {
// return;
// }
// }
// }
// throw new AccessDeniedException("当前访问没有权限");
// }
//
// @Override
// public boolean supports(ConfigAttribute attribute) {
// // TODO Auto-generated method stub
// return false;
// }
//
// @Override
// public boolean supports(Class<?> clazz) {
// // TODO Auto-generated method stub
// return false;
// }
//
// });
// return object;
// }
//
//
// })
// .and()
// .exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {//权限不足自定义处理
//
// @Override
// public void handle(HttpServletRequest request, HttpServletResponse response,
// AccessDeniedException accessDeniedException) throws IOException, ServletException {
// // TODO Auto-generated method stub
// response.setStatus(HttpServletResponse.SC_FORBIDDEN);
// response.setContentType("application/json;charset=UTF-8");
// PrintWriter out = response.getWriter();
// out.write("权限错误");
// out.flush();
// out.close();
// }
// });
}
}
因篇幅问题不能全部显示,请点此查看更多更全内容