Spring Security 的验证作业实际是交由``AuthenticationProvider的实作来执行,如
DaoAuthenticationProvider进行**使用者名称**和**密码**的身分验证,而在验证方法中会透过呼叫
UserDetailsService.loadUserByUsername(String username)查询使用者资讯
UserDetails` ,然後比对使用者资讯与输入的密码是否相同来验证其是否为合法的使用者。
Spring Security 预设所有的路径都必须先经过身分验证才可以存取,因此在新增依赖後要记得设置验证授权规则,否则一律会收到HTTP 401(Unauthorized) 的状态码。
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
设置Spring Security 的授权规则必须继承WebSecurityConfigurerAdapter
并加上@EnableSecurity
注释,让该类别的安全配置生效。
而WebSecurityConfigurerAdapter
有三个重要的configure 可以覆写,一个与验证相关的AuthenticationManagerBuilder
,另外两个是与Web 相关的HttpSecurity
和WebSecurity
。
AuthenticationManagerBuilder
: 用来配置全局的验证资讯,也就是AuthenticationProvider
和UserDetailsService
。WebSecurity
: 用来配置全局忽略的规则,如静态资源、是否Debug、全局的HttpFirewall、SpringFilterChain 配置、privilegeEvaluator、expressionHandler、securityInterceptor。HttpSecurity
: 用来配置各种具体的验证机制规则,如OpenIDLoginConfigurer、AnonymousConfigurer、FormLoginConfigurer、HttpBasicConfigurer 等。package com.example.iThomeIronMan.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// TODO Auto-generated method stub
auth.userDetailsService(userDetailsService)
.passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// TODO Auto-generated method stub
http.authorizeRequests()
// 设定放行名单
.antMatchers("/login", "/register").permitAll()
// 其余路径皆须进行验证
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").usernameParameter("account").passwordParameter("password")
.and()
.logout().logoutUrl("/logout")
.and()
// 关闭CSRF(跨站请求伪造)攻击的防护,这样才不会拒绝外部直接对API 发出的请求,例如Postman 与前端
.csrf().disable();
}
@Override
public void configure(WebSecurity web) throws Exception {
// TODO Auto-generated method stub
web.ignoring().antMatchers("/css/**", "/images/**", "/js/**");
}
}
package com.example.iThomeIronMan.model;
import java.util.Collection;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class MemberAccount extends Base implements UserDetails {
private static final long serialVersionUID = 1L;
private String id;
@Email(message = "帐号必须为电子信箱格式")
@NotBlank(message = "帐号不可为空")
private String account;
@NotBlank(message = "密码不可为空")
@Pattern(regexp = "^(?=.*[a-z])(?=.*[0-9])[a-zA-Z]{1}[a-zA-Z0-9]{5,15}$",
message = "密码必须为6 至16 位英文及数字组成且首位字元为英文。")
private String password;
private String salt;
@Override
// 取得所有权限
public Collection<? extends GrantedAuthority> getAuthorities() {
// TODO Auto-generated method stub
return null;
}
@Override
// 取得使用者名称
public String getUsername() {
// TODO Auto-generated method stub
return account;
}
@Override
// 取得密码
public String getPassword() {
// TODO Auto-generated method stub
return password;
}
@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;
}
}
package com.example.iThomeIronMan.service.impl;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.iThomeIronMan.dao.MemberAccountDao;
import com.example.iThomeIronMan.model.Member;
import com.example.iThomeIronMan.model.MemberAccount;
import com.example.iThomeIronMan.service.MemberAccountService;
import com.example.iThomeIronMan.service.MemberService;
import com.example.iThomeIronMan.service.ex.AccountDuplicateException;
import com.example.iThomeIronMan.service.ex.InsertException;
@Service
public class MemberAccountServiceImpl implements MemberAccountService, UserDetailsService {
@Autowired
private MemberAccountDao memberAccountDao;
@Autowired
private MemberService memberService;
private BCryptPasswordEncoder passwordEncoder;
public MemberAccountServiceImpl() {
this.passwordEncoder = new BCryptPasswordEncoder();
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// TODO Auto-generated method stub
MemberAccount data = memberAccountDao.getMemberAccountByAccount(username);
if(data == null) throw new UsernameNotFoundException("无此帐号");
return data;
}
@Transactional
public String register(MemberAccount memberAccount, String name) {
// 检查帐号是否已被注册
MemberAccount data = memberAccountDao.getMemberAccountByAccount(memberAccount.getAccount());
if(data != null) throw new AccountDuplicateException("该帐号已被注册");
// 产生盐值
String salt = UUID.randomUUID().toString().toUpperCase().replaceAll("-", "");
memberAccount.setSalt(salt);
// 密码加密
String encoderPassword = passwordEncoder.encode(memberAccount.getPassword());
memberAccount.setPassword(encoderPassword);
// 新增帐号
memberAccount.setCreate_by(memberAccount.getAccount());
memberAccount.setUpdate_by(memberAccount.getAccount());
Integer id = memberAccountDao.add(memberAccount);
if(id == 0) throw new InsertException("新增帐号时发生错误");
// 新增会员资讯
Member member = new Member();
member.setMa_id(String.valueOf(id));
member.setName(name);
member.setCreate_by(memberAccount.getAccount());
member.setUpdate_by(memberAccount.getAccount());
Integer result = memberService.add(member);
if(result == 0) throw new InsertException("新增帐号时发生错误");
return null;
}
public Member login(MemberAccount memberAccount) {
// 检查帐号是否存在
MemberAccount data = memberAccountDao.getMemberAccountByAccount(memberAccount.getAccount());
if(data == null) {
return null;
}
// 密码加密
String encoderPassword = passwordEncoder.encode(memberAccount.getPassword());
// 比对密码
if(!data.getPassword().equals(encoderPassword)) {
return null;
}
// 取得会员资讯
return memberService.getDataByMa_id(data.getId());
}
}
Spring Security(二)WebSecurityConfigurer配置以及filter顺序
观察的视角 我们要如何描述一个系统呢? 可以从不同的角度观察,好比瞎子摸象,你摸到甚麽部位,系统就像...
嗨,昨天语料库模型建好了,下一步要如何使用呢? 我们要如何比对输入的句子与语料库中的哪一句最相似呢?...
Hello大家, 台北阴雨绵绵, 早上到公司裤管都湿答答的="= 不舒服... 今天我们来...
昨天有讲到怎麽利用vue-router来设定路由了, 在开始切版之前,还需要先导入Vuetify套件...
我第一次在微软 release 产品时,学到一件很意外的事是:这世界上政治敏感区域原来不只有台湾跟中...