我的開(kāi)發(fā)環(huán)境是springboot(下面簡(jiǎn)稱(chēng)sb)+freemarker+maven
如大家所知道的,sb里面沒(méi)有配置文件嚎京,需要通過(guò)類(lèi)加載或者解析xml來(lái)配置一些東西,這也是初學(xué)sb的一個(gè)頭痛點(diǎn)。(我當(dāng)時(shí)就是准脂,哈哈)
好的不多嗶嗶,直入正題檬洞。但是我還是想告誡一些沒(méi)有sb讀配置文件的經(jīng)驗(yàn)的小司機(jī)狸膏。可能會(huì)不好理解添怔,但是不要放棄湾戳,一步一步跟著我來(lái),就可以
1. pom依賴(lài)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
</dependency>
第一個(gè)依賴(lài)是security的核心依賴(lài)包广料,第二個(gè)是security對(duì)freemarker的標(biāo)簽的支持包砾脑。
加了這兩個(gè)東西之后,重啟項(xiàng)目艾杏,它就會(huì)彈出讓你登錄的窗口韧衣。
你可能會(huì)迷惑,我什么都沒(méi)配置购桑,為什么就給我要密碼畅铭?因?yàn)閟ecurity依賴(lài)包只要引入了,它會(huì)默認(rèn)給項(xiàng)目加入密碼勃蜘。用戶(hù)名是user 默認(rèn)密碼在控制臺(tái)找下硕噩。
實(shí)際開(kāi)發(fā)中,我們肯定不是這樣讓用戶(hù)認(rèn)證缭贡。所以我們要重寫(xiě)security的實(shí)現(xiàn)類(lèi)炉擅。
2. 繼承重寫(xiě)WebSecurityConfigurerAdapter類(lèi)
/**
*
* Created by Fant.J.
* 2017/10/26 18:48
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
//返回 BCrypt 加密對(duì)象
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
// @Autowired
// private AuthenticationSuccessHandler myAuthenticationSuccessHandler;
// @Autowired
// private MyAuthenticationFailHandler myAuthenticationFailHandler;
@Bean
protected UserDetailsService userDetailServiceImpl(){
return new UserDetailServiceImpl();
}
@Override
protected void configure(HttpSecurity http)throws Exception{
// ValidateCodeFilter filter = new ValidateCodeFilter();
// //寫(xiě)入自定義錯(cuò)誤過(guò)濾器
// filter.setAuthenticationFailureHandler(myAuthenticationFailHandler);
//表單登錄
http.formLogin()
// http.httpBasic()
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
http
.headers()
.frameOptions()
.sameOrigin();
}
}
http.formLogin() 表示以表單的形式登錄,authorizeRequests()表示什么請(qǐng)求需要驗(yàn)證呢阳惹?anyRequest()嗯坑资,所有的請(qǐng)求都需要驗(yàn)證。authenticated();證明是有效的情況下穆端。
自己整段話連起來(lái)袱贮,大家就懂了這種寫(xiě)法的含義了。
相信一些有心人在上面的類(lèi)中看到了這段代碼
@Bean
protected UserDetailsService userDetailServiceImpl(){
return new UserDetailServiceImpl();
}
我們來(lái)看下這個(gè)UserDetailsService這個(gè)官方接口根據(jù)方法名我們能看出來(lái),它是用來(lái)通過(guò)username加載User信息的攒巍,這和form驗(yàn)證就關(guān)系上了嗽仪。你只需要把這個(gè)類(lèi)注入到類(lèi)里,它會(huì)自動(dòng)去找這個(gè)實(shí)現(xiàn)類(lèi)柒莉,那么下面我們來(lái)看下我寫(xiě)的這個(gè)實(shí)現(xiàn)類(lèi)闻坚。
3. UserDetailServiceImpl 實(shí)現(xiàn)UserDetailsService接口
/**
* Created by Fant.J.
* 2017/10/26 20:54
*/
@Component
@Slf4j
public class UserDetailServiceImpl implements UserDetailsService{
@Autowired
UserAdminMapper userAdminMapper;
@Autowired
private PasswordEncoder passwordEncoder;
/** 超級(jí)用戶(hù) */
private static final Integer USER_POWER_ADMIN = 1;
/** 普通用戶(hù) */
private static final Integer USER_POWER_USER = 2;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
try {
UserAdmin userAdmin = userAdminMapper.selectByUsername(username);
log.info("登錄用戶(hù)名" + username);
log.info("數(shù)據(jù)庫(kù)密碼" + userAdmin.getPassword());
if (userAdmin == null) {
throw new UsernameNotFoundException("沒(méi)有找到");
}
//獲取用戶(hù)使用狀態(tài)
boolean status = false;
if (userAdmin.getUserStatus() == 1){
status = true;
}
if (userAdmin.getUserPower() == 1) {
log.info("ADMIN用戶(hù)"+userAdmin.getUserAdminName()+"登錄");
return new User(username, passwordEncoder.encode(userAdmin.getPassword()),
status, true, true, true,
AuthorityUtils.commaSeparatedStringToAuthorityList("ADMIN"));
} else if (userAdmin.getUserPower() == 2) {
log.info("ADMIN用戶(hù)"+userAdmin.getUserAdminName()+"登錄");
return new User(username, passwordEncoder.encode(userAdmin.getPassword()),
true, true, true, true,
AuthorityUtils.commaSeparatedStringToAuthorityList("USER"));
} else {
log.error("用戶(hù)權(quán)限錯(cuò)誤異常,默認(rèn)為USER普通用戶(hù)");
return new User(username, passwordEncoder.encode(userAdmin.getPassword()),
true, true, true, true,
AuthorityUtils.commaSeparatedStringToAuthorityList("USER"));
}
}catch (Exception e){
log.error("用戶(hù)權(quán)限錯(cuò)誤異常"+e.getMessage());
throw new MyException("用戶(hù)權(quán)限錯(cuò)誤異常");
}
}
}
返回類(lèi)型必須是UserDetails(官方提供類(lèi)),來(lái)看下它的源碼
可以看到里面包含了賬號(hào)密碼,和是否過(guò)期兢孝,是否可用窿凤,是否被鎖,是否有效四個(gè)狀態(tài)跨蟹。所以我代碼里有
return new User(username, passwordEncoder.encode(userAdmin.getPassword()), status, true, true, true, AuthorityUtils.commaSeparatedStringToAuthorityList("ADMIN"));
大家就能看懂了雳殊,
AuthorityUtils.commaSeparatedStringToAuthorityList("ADMIN"));
這個(gè)是自定義添加的用戶(hù)身份,再開(kāi)發(fā)過(guò)程中可把它封裝起來(lái)窗轩。(我在這里由于數(shù)據(jù)庫(kù)設(shè)計(jì)原因定成死的)夯秃,說(shuō)明它是個(gè)admin身份。passwordEncoder.encode
是security提供的加密痢艺,(挺高端的仓洼,隨機(jī)生成鹽然后插入到密碼里。每次登錄都不一樣堤舒,還提供了passwordEncoder.match()方法色建,兩者聯(lián)合判斷用戶(hù)是否有效),不多說(shuō) 看源碼然后你返回去看我的SecurityConfig類(lèi)舌缤,你會(huì)找到
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
這個(gè)注入類(lèi)就是獲取impl中從數(shù)據(jù)庫(kù)里查出來(lái)的密碼镀岛,然后match判斷。所以就要求我們注冊(cè)用戶(hù)的時(shí)候友驮,先encode()一下再存入數(shù)據(jù)庫(kù)漂羊,然后讀出來(lái)直接返回,我的這個(gè)impl是從數(shù)據(jù)庫(kù)里讀出密碼然后加密的卸留,因?yàn)槲遗聰?shù)據(jù)庫(kù)密碼我都會(huì)忘記走越。。
按照需求來(lái)吧耻瑟。
4. 啟動(dòng)項(xiàng)目
security默認(rèn)有個(gè)form表單提交頁(yè)面旨指。我們輸入錯(cuò)誤的密碼。
輸入錯(cuò)誤的賬號(hào)
看上去沒(méi)有提示喳整,因?yàn)槲覀儧](méi)有捕獲這個(gè)異常谆构。
我們輸入正確的帳號(hào)密碼
進(jìn)來(lái)了。但是測(cè)試的時(shí)候你會(huì)發(fā)現(xiàn)個(gè)問(wèn)題框都。表單提交的時(shí)候會(huì)報(bào)403無(wú)權(quán)限訪問(wèn)異常
搬素。好好想想我們引入了兩個(gè)依賴(lài)包,第二個(gè)還沒(méi)用呢!
在每個(gè)有form提交的地方都加上
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
這是security框架token認(rèn)證需要的東西熬尺。