目录
1.简介
与shiro对比
添加依赖
执行流程
2.UserDetailsService
User实现类
3.PasswordEncoder
BCryptPasswordEncoder
4.自定义登录逻辑
5.自定义登录界面
6.设置请求账户和密码的参数
7.自定义登陆处理器
成功
失败
8.判断
权限判断
角色判断
ip判断
9.基于注解开发
@Secured
@PreAuthorize/@PostAuthorize
10.记住我功能
导入依赖
自定义
11.未完待续...
Spring Security是一个Java框架,用于保护应用程序的安全性。它提供了一套全面的安全解决方案,包括身份验证、授权、防止攻击等功能。Spring Security基于过滤器链的概念,可以轻松地集成到任何基于Spring的应用程序中。它支持多种身份验证选项和授权策略,开发人员可以根据需要选择适合的方式。此外,Spring Security还提供了一些附加功能,如集成第三方身份验证提供商和单点登录,以及会话管理和密码编码等。总之,Spring Security是一个强大且易于使用的框架,可以帮助开发人员提高应用程序的安全性和可靠性。
Apache Shiro是一个强大且易用的Java安全框架,能够非常清晰的处理认证、授权、管理会话以及密码加密。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
ssm+shrio
springBoot+springSecurity
org.springframework.boot
spring-boot-starter-security
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
通过传入的用户名来判断,如果要自定义逻辑来进行判断只需要实现UserDetailsService接口,而该接口又继承了UserDetails接口
public interface UserDetails extends Serializable {
/**
* Returns the authorities granted to the user. Cannot return null
.
* @return the authorities, sorted by natural key (never null
)
*/
Collection extends GrantedAuthority> getAuthorities();
/**
* Returns the password used to authenticate the user.
* @return the password
*/
String getPassword();
/**
* Returns the username used to authenticate the user. Cannot return
* null
.
* @return the username (never null
)
*/
String getUsername();
/**
* Indicates whether the user's account has expired. An expired account cannot be
* authenticated.
* @return true
if the user's account is valid (ie non-expired),
* false
if no longer valid (ie expired)
*/
boolean isAccountNonExpired();
/**
* Indicates whether the user is locked or unlocked. A locked user cannot be
* authenticated.
* @return true
if the user is not locked, false
otherwise
*/
boolean isAccountNonLocked();
/**
* Indicates whether the user's credentials (password) has expired. Expired
* credentials prevent authentication.
* @return true
if the user's credentials are valid (ie non-expired),
* false
if no longer valid (ie expired)
*/
boolean isCredentialsNonExpired();
/**
* Indicates whether the user is enabled or disabled. A disabled user cannot be
* authenticated.
* @return true
if the user is enabled, false
otherwise
*/
boolean isEnabled();
}
这个user不是我们自定义的User类,他继承了UserDetails接口,通过客户端传入的username在去数据库匹配对应的密码,然后通过查询出来的密码与前端进行匹配
public User(String username, String password, boolean enabled, boolean accountNonExpired,
boolean credentialsNonExpired, boolean accountNonLocked,
Collection extends GrantedAuthority> authorities) {
Assert.isTrue(username != null && !"".equals(username) && password != null,
"Cannot pass null or empty values to constructor");
this.username = username;
this.password = password;
this.enabled = enabled;
this.accountNonExpired = accountNonExpired;
this.credentialsNonExpired = credentialsNonExpired;
this.accountNonLocked = accountNonLocked;
this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
}
public interface PasswordEncoder {
/**
* Encode the raw password. Generally, a good encoding algorithm applies a SHA-1 or
* greater hash combined with an 8-byte or greater randomly generated salt.
*/
String encode(CharSequence rawPassword);//加密密码
/**
* Verify the encoded password obtained from storage matches the submitted raw
* password after it too is encoded. Returns true if the passwords match, false if
* they do not. The stored password itself is never decoded.
* @param rawPassword the raw password to encode and match
* @param encodedPassword the encoded password from storage to compare with
* @return true if the raw password, after encoding, matches the encoded password from
* storage
*/
boolean matches(CharSequence rawPassword, String encodedPassword);//原密码和加密密码进行匹配
/**
* Returns true if the encoded password should be encoded again for better security,
* else false. The default implementation always returns false.
* @param encodedPassword the encoded password to check
* @return true if the encoded password should be encoded again for better security,
* else false.
*/
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
官方推荐的一个实现类,基于哈希算法进行加密
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//1.模拟数据库查询用户名是否存在否则抛出异常UsernameNotFoundException
if(!"admin".equals(username)){
throw new UsernameNotFoundException("用户名不存在!");
}
//2.把加密过的密码进行解析
String password = passwordEncoder.encode("123");
return new User(username,password,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));
}
}
重定向时只能用@Controller,不能用@RestController
@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder getPw(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
//当发现是login时认为是登录,必须和表单提供的地址一致去执行UserDetailsServiceImpl
.loginProcessingUrl("/login")
//自定义登录界面
.loginPage("/login.html")
//登录成功后必须是post请求
.successForwardUrl("/toMain")
//登录失败后跳转
.failureForwardUrl("/toError");
//认证授权
http.authorizeRequests()
//登录失败放行不需要认证
.antMatchers("/error.html").permitAll()
//登录放行不需要认证
.antMatchers("/login.html").permitAll()
//所有请求都被拦截类似于mvc必须登录后访问
.anyRequest().authenticated();
//关闭csrf防护
http.csrf().disable();
}
}
/**
* 页面跳转
* @return
*/
@RequestMapping("/toMain")
public String toMain(){
return "redirect:main.html";
}
/**
* 页面失败跳转
* @return
*/
@RequestMapping("/toError")
public String toError(){
return "redirect:error.html";
}
登录的表单name为username和password与UsernamePasswordAuthenticationFilter中相对应,如果我们想要修改这个属性名可以通过usernameParameter()和passwordParameter()进行修改
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
由于前后端分离项目,页面跳转大多都在web浏览器中进行,这时successForwardUrl()和failureForwardUrl()方法就会失效,所以我们就要自定义方法进行实现且不能与successForwardUrl和failureForwardUrl共存
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private String url;
public MyAuthenticationSuccessHandler(String url) {
this.url = url;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response
, Authentication authentication) throws IOException, ServletException {
User user = (User) authentication.getPrincipal();
System.out.println(user.getAuthorities());
System.out.println(user.getUsername());
System.out.println(user.getPassword());//密码被保护会输出null
response.sendRedirect(url);
}
}
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
private String url;
public MyAuthenticationFailureHandler(String url) {
this.url = url;
}
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
response.sendRedirect(url);
}
}
在自定义逻辑中创建的User中指定,严格区分大小写
return new User(username,password,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));
.antMatchers("/main1.html").hasAnyAuthority("admin")
return new User(username,password,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_abc"));
.antMatchers("/main1.html").hasAnyRole("abc")
localhost和127.0.0.1的ip是不一样的需要指定特定的ip地址
在启动类中添加注解@EnableGlobalMethodSecurity(securedEnabled = true)
在controller层中添加
@Secured("ROLE_abc")
@RequestMapping("/toMain")
public String toMain(){
return "redirect:main.html";
}
方法或类级别的注解
@PreAuthorize("hasRole('ROLE_abc')")
PreAuthorize表达式允许ROLE开头,也可以不用ROLE开头
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.2.2
mysql
mysql-connector-java
在config中配置
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private DataSource dataSource;
@Autowired
PersistentTokenRepository persistentTokenRepository;
http.rememberMe()
.tokenValiditySeconds(60)
//自定义登录逻辑
.userDetailsService(userDetailsService)
//持久层对象
.tokenRepository(persistentTokenRepository);//底层靠jdbc实现
@Bean
public PersistentTokenRepository getPersistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
//第一次启动时建表,第二次使用时注释掉
//jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}