看完shiro,在看spring security感覺快了很多,最開始看spring security的時候,非常暈,看完我覺得spring security做了太多事,以至於程序員都不知道,是怎麼實現的,這樣的
後果就是 當出現錯誤,或者需要修改的時候感覺無從下手。
個人理解,若有錯誤,請指正。
spring security跟shiro類似,都是使用過濾器來認證和授權,不同的是spring seciruty是實現了一個過濾器鏈,每個請求都要經過,我們可以使用自動配置,這樣spring security自動幫我們配置了這一系列過濾器,也可以自定義過濾器放在它的過濾器鏈中。
驗證碼或密碼登錄,需要重新修改認證過濾器
package com.test.hello.security;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationServiceException;
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;
public class KdUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter{
private boolean postOnly = true;
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
String password = obtainPassword(request);
String type = request.getParameter("j_type");
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
if (type == null) {
type = "1";
}
username = username.trim();
Authentication authRequest;
if(type.equals("1")){
authRequest = new UsernamePasswordAuthenticationToken(username, password);
}else{
authRequest = new KdUsernamePasswordAuthenticationToken(username, password,type);
}
// Allow subclasses to set the "details" property
setDetails(request, (AbstractAuthenticationToken)authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
/**
* Provided so that subclasses may configure what is put into the authentication request's details
* property.
*
* @param request that an authentication request is being created for
* @param authRequest the authentication request object that should have its details set
*/
protected void setDetails(HttpServletRequest request, AbstractAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
}
type爲2時候,使用驗證碼登錄,token- >provider ->
token
package com.test.hello.security;
import java.util.Collection;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
public class KdUsernamePasswordAuthenticationToken extends AbstractAuthenticationToken{
//~ Instance fields ================================================================================================
/**
*
*/
private static final long serialVersionUID = 1L;
private final Object principal;
private Object credentials;
private String type;
//~ Constructors ===================================================================================================
/**
* This constructor can be safely used by any code that wishes to create a
* <code>UsernamePasswordAuthenticationToken</code>, as the {@link
* #isAuthenticated()} will return <code>false</code>.
*
*/
public KdUsernamePasswordAuthenticationToken(Object principal, Object credentials,String type) {
super(null);
this.principal = principal;
this.credentials = credentials;
this.type = type;
setAuthenticated(false);
}
/**
* This constructor should only be used by <code>AuthenticationManager</code> or <code>AuthenticationProvider</code>
* implementations that are satisfied with producing a trusted (i.e. {@link #isAuthenticated()} = <code>true</code>)
* authentication token.
*
* @param principal
* @param credentials
* @param authorities
*/
public KdUsernamePasswordAuthenticationToken(Object principal, Object credentials,String type, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
this.type = type;
super.setAuthenticated(true); // must use super, as we override
}
//~ Methods ========================================================================================================
public Object getCredentials() {
return this.credentials;
}
public Object getPrincipal() {
return this.principal;
}
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException(
"Once created you cannot set this token to authenticated. Create a new instance using the constructor which takes a GrantedAuthority list will mark this as authenticated.");
}
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
credentials = null;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
provider 重寫了 密碼校驗方法,並且默認使用了KdJdbcDaoImpl去查詢用戶信息
KdAbstractUserDetailsAuthenticationProvider跟AbstractUserDetailsAuthenticationProvider一樣僅僅改了authenticate方法裏面的
Assert.isInstanceOf(KdUsernamePasswordAuthenticationToken.class,
package com.test.hello.security;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
public class KdDaoAuthenticationProvider extends KdAbstractUserDetailsAuthenticationProvider{
private UserDetailsService userDetailsService = new KdJdbcDaoImpl();
public UserDetailsService getUserDetailsService() {
return userDetailsService;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Override
public boolean supports(Class<?> authentication) {
return (KdUsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
@SuppressWarnings("deprecation")
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails,
KdUsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails);
}
String presentedPassword = authentication.getCredentials().toString();
if (!userDetails.getPassword().equals(presentedPassword)) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails);
}else{
KdJdbcDaoImpl.userpass.remove(userDetails.getUsername());
}
}
@Override
protected UserDetails retrieveUser(String username,
KdUsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
UserDetails loadedUser;
try {
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
} catch (UsernameNotFoundException notFound) {
throw notFound;
} catch (Exception repositoryProblem) {
throw new InternalAuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem);
}
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
}
KdJdbcDaoImpl
package com.test.hello.security;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl;
public class KdJdbcDaoImpl extends JdbcDaoImpl{
public static ConcurrentHashMap<String,String> userpass = new ConcurrentHashMap<String, String>();
@Override
protected List<UserDetails> loadUsersByUsername(String username) {
List<UserDetails> list = new ArrayList<UserDetails>();
String password = userpass.get(username);
if(password != null){
list.add(new User(username, password, true, true, true, true, AuthorityUtils.NO_AUTHORITIES));
}
return list;
}
}
最後的配置
<security:http pattern="/resource/**" security="none"></security:http>
<security:http pattern="/index.jsp" security="none"></security:http>
<security:http pattern="/" security="none"></security:http>
<security:http pattern="/checkcode" security="none"></security:http>
<security:http entry-point-ref="loginUrlAuthenticationEntryPoint" >
<security:intercept-url pattern="/role/user**" access="ROLE_USER"/>
<security:intercept-url pattern="/role/admin**" access="ROLE_ADMIN"/>
<security:intercept-url pattern="/role/manager**" access="ROLE_MANAGER"/>
<security:custom-filter ref="kdUsernamePasswordAuthenticationFilter" position="FORM_LOGIN_FILTER"/>
<security:custom-filter ref="logoutFilter" position="LOGOUT_FILTER"/>
<security:access-denied-handler ref="accessDeniedHandlerImpl"/>
</security:http>
<bean id="accessDeniedHandlerImpl" class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<property name="errorPage" value="/errorPage.jsp"></property>
</bean>
<bean id="loginUrlAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<property name="loginFormUrl" value="/index.jsp"></property>
</bean>
<bean id="kdUsernamePasswordAuthenticationFilter" class="com.test.hello.security.KdUsernamePasswordAuthenticationFilter">
<property name="authenticationManager" ref="org.springframework.security.authenticationManager"></property>
<property name="authenticationSuccessHandler" ref="savedRequestAwareAuthenticationSuccessHandler"></property>
<property name="authenticationFailureHandler" ref="simpleUrlAuthenticationFailureHandler"></property>
</bean>
<bean id="savedRequestAwareAuthenticationSuccessHandler" class="com.test.hello.security.KdSavedRequestAwareAuthenticationSuccessHandler">
<property name="defaultTargetUrl" value="/loginSuccess"></property>
</bean>
<bean id="simpleUrlAuthenticationFailureHandler" class="com.test.hello.security.KdSimpleUrlAuthenticationFailureHandler">
<property name="defaultFailureUrl" value="/index.jsp"></property>
</bean>
<bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
<property name="filterProcessesUrl" value="/logout"></property>
<constructor-arg index="0" >
<bean class="org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler">
<property name="defaultTargetUrl" value="/index.jsp"></property>
</bean>
</constructor-arg>
<constructor-arg index="1">
<array>
<bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"></bean>
</array>
</constructor-arg>
</bean>
<bean id="org.springframework.security.authenticationManager" name="authenticationManager" class="org.springframework.security.authentication.ProviderManager" >
<property name="authenticationEventPublisher" ref="defaultAuthenticationEventPublisher"></property>
<property name="providers">
<list>
<ref bean="daoAuthenticationProvider"/>
<ref bean="anonymousAuthenticationProvider"/>
<ref bean="kdDaoAuthenticationProvider"/>
</list>
</property>
</bean>
<bean id="defaultAuthenticationEventPublisher" class="org.springframework.security.authentication.DefaultAuthenticationEventPublisher"></bean>
<bean id="anonymousAuthenticationProvider" class="org.springframework.security.authentication.AnonymousAuthenticationProvider">
<property name="key" value="work"></property>
</bean>
<bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="userService"></property>
<!-- <property name="passwordEncoder" ref="bcrypt"></property> -->
</bean>
<bean id="userService" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="kdDaoAuthenticationProvider" class="com.test.hello.security.KdDaoAuthenticationProvider">
<property name="userDetailsService" ref="kdJdbcDaoImpl"></property>
</bean>
<bean id="kdJdbcDaoImpl" class="com.test.hello.security.KdJdbcDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<base href="<%=basePath%>">
<title>My JSP 'index.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<script type="text/javascript" src="resource/js/jquery.min.js"></script>
<script type="text/javascript">
$(function(){
$("#jtypese").change(function(){
var v = $("#jtypese").val();
if(v=='2'){
var uname = $("#usernameinput").val();
var url = "<%=basePath%>checkcode?username="+uname;
$.get(url,function(data,status){
alert("驗證碼: " + data + "\n發送狀態: " + status);
});
}
});
});
</script>
</head>
<body>
This is my login page. <br>
<form action="j_spring_security_check" method="post">
<table>
<tr><td>用戶名:</td><td><input name="j_username" value="u1" id="usernameinput"></td></tr>
<tr><td>密碼/驗證碼:</td><td><input name="j_password" value="p1"></td></tr>
<tr><td>登錄方式: </td><td><select name="j_type" id="jtypese"><option value="1">密碼</option><option value="2">驗證碼</option></select></td></tr>
<tr><td colspan="2"> <input type="submit" value="submit"></td></tr>
</table>
</form>
異常: ${SPRING_SECURITY_LAST_EXCEPTION }<br>
失敗次數: ${SPRING_SESSION_FAIL_TIMES }
</body>
</html>