<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
让User实体继承UserDetails接口,实现重写方法。
public class User implements UserDetails {
//true:账号未过期
@Override
public boolean isAccountNonExpired() {
return true;
}
//true:账号未锁定
@Override
public boolean isAccountNonLocked() {
return true;
}
//true:凭证未1过期
@Override
public boolean isCredentialsNonExpired() {
return true;
}
//true:账号可用
@Override
public boolean isEnabled() {
return true;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
//一个GrantedAuthority代表一个权限
List<GrantedAuthority> list = new ArrayList<>();
list.add(new GrantedAuthority() {
@Override
public String getAuthority() {
switch (type){
case 1:
return "ADMIN";
default:
return "USER";
}
}
});
return list;
}
让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);
}
}
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
@Override
public void configure(WebSecurity web) throws Exception {
//忽略静态资源的访问提高性能
web.ignoring().antMatchers("/resources/**");
}
// AuthorizationManager:认证的核心接口
// AuthorizationManagerBuilder:用于构建AuthenticationManager对象的工具
// ProvideManager:AuthenticationManager接口的默认实现类。
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//内置的认证规则
//new Pbkdf2PasswordEncoder("12345") 内置加密工具
//auth.userDetailsService(userService).passwordEncoder(new Pbkdf2PasswordEncoder("12345"));
//自定义认证规则
//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().equals(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.setAttribute("error",e.getMessage());
request.getRequestDispatcher("/loginpage").forward(request,response);
}
});
//退出相关配置
http.logout()
.logoutSuccessUrl("/logout")
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.sendRedirect(request.getContextPath() + "/index");
}
});
//授权配置
http.authorizeRequests()
.antMatchers("/letter").hasAnyAuthority("USER","ADMIN")
.antMatchers("/admin").hasAnyAuthority("ADMIN")
.and().exceptionHandling().accessDeniedPage("/denied");
//增加Filter,处理验证码
http.addFilterBefore(new Filter() {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) 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;
}
}
//让请求继续向下走,让请求放行
filterChain.doFilter(request,response);
}
}, UsernamePasswordAuthenticationFilter.class);
//记住我
http.rememberMe()
.tokenRepository(new InMemoryTokenRepositoryImpl())
.tokenValiditySeconds(3600 * 24)
.userDetailsService(userService);
}
}
/**
* 权限:普通用户
*/
String AUTHORITY_USER = "user";
/**
* 权限:管理员
*/
String AUTHORITY_MODERATOR = "moderator";
重写
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter implements CommunityConstant {
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//授权
http.authorizeRequests()
.antMatchers(
"/user/setting",
"/user/upload",
"/discuss/add",
"/comment/add/**",
"/letter/**",
"notice/**",
"/like",
"/follow",
"/unfollow"
)
.hasAnyAuthority(
AUTHORITY_USER,
AUTHORITY_ADMIN,
AUTHORITY_MODERATOR
)
.anyRequest().permitAll()
.and().csrf().disable() ;
//权限不够的时候处理
http.exceptionHandling()
.authenticationEntryPoint(new AuthenticationEntryPoint() {
//没有登录
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
String xRequestedWith = request.getHeader("x-requested-with");
if ("XMLHttpRequest".equals(xRequestedWith)){
response.setContentType("application/plain;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(CommunityUtil.getJSONString(403,"你还没有登录哦!"));
}else {
response.sendRedirect(request.getContextPath() + "/login");
}
}
})
.accessDeniedHandler(new AccessDeniedHandler() {
//权限不够
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
String xRequestedWith = request.getHeader("x-requested-with");
if ("XMLHttpRequest".equals(xRequestedWith)){
response.setContentType("application/plain;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(CommunityUtil.getJSONString(403,"你没有访问此功能的权限!!"));
}else {
response.sendRedirect(request.getContextPath() + "/ denied");
}
}
});
//security底层默认会拦截/logout请求,进行退出处理。
//覆盖它默认的逻辑,才能执行我们自己的退出代码
//security首先是将用户token和权限存入securityContext里,登录的时候从Context中获取相关的权限
http.logout().logoutUrl("/securitylogout");
}
}
public Collection<? extends GrantedAuthority> getAuthorities(int userId){
User user = this.findUserById(userId);
List<GrantedAuthority> list = new ArrayList<>();
list.add(new GrantedAuthority() {
@Override
public String getAuthority() {
switch (user.getType()) {
case 1:
return AUTHORITY_ADMIN;
case 2:
return AUTHORITY_MODERATOR;
default:
return AUTHORITY_USER;
}
}
});
return list;
}
在LoginTicketInterceptor中的preHandle和afterHandle中添加代码
当你浏览器访问网站A的时候,cookie和ticket存在浏览器,然后你再访问了有病毒的X网站。则X网站则会窃取你浏览器中的cookie和ticket发送请求给服务器进行一些不正当的操作。这种CRSF攻击发生在提交表单的时候。
解决方法就是,提交表单给服务器后服务器返回数据给浏览器的时候,带一个tocken一起返回。此时有病毒的网站只能获取你的cookie和ticket但不能获取你的tocken。